Android의 RecyclerView.State 에 대한 질문이 있습니다 .
RecyclerView를 사용하고 있는데 RecyclerView.State와 어떻게 사용하고 바인딩 할 수 있습니까?
내 목적은 RecyclerView의 스크롤 위치 를 저장하는 것입니다 .
Android의 RecyclerView.State 에 대한 질문이 있습니다 .
RecyclerView를 사용하고 있는데 RecyclerView.State와 어떻게 사용하고 바인딩 할 수 있습니까?
내 목적은 RecyclerView의 스크롤 위치 를 저장하는 것입니다 .
답변:
마지막으로 저장된 위치를 RecyclerView.State
어떻게 저장할 계획 입니까?
항상 좋은 저장 상태에 의존 할 수 있습니다. RecyclerView
onSaveInstanceState ()를 확장 하고 재정의합니다 onRestoreInstanceState()
.
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
SavedState newState = new SavedState(superState);
newState.mScrollPosition = mScrollPosition;
return newState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
if(state != null && state instanceof SavedState){
mScrollPosition = ((SavedState) state).mScrollPosition;
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null){
int count = layoutManager.getItemCount();
if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
layoutManager.scrollToPosition(mScrollPosition);
}
}
}
}
static class SavedState extends android.view.View.BaseSavedState {
public int mScrollPosition;
SavedState(Parcel in) {
super(in);
mScrollPosition = in.readInt();
}
SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mScrollPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
recyclerview:1.2.0-alpha02
출시 부터 StateRestorationPolicy
도입되었습니다. 주어진 문제에 대한 더 나은 접근 방식이 될 수 있습니다.
이 주제는 안드로이드 개발자 중간 기사 에서 다루었습니다 .
또한 @ rubén-viguera는 아래 답변에서 자세한 내용을 공유했습니다. https://stackoverflow.com/a/61609823/892500
LinearLayoutManager를 사용하는 경우 미리 빌드 된 저장 API linearLayoutManagerInstance.onSaveInstanceState () 및 복원 api linearLayoutManagerInstance.onRestoreInstanceState (...)가 함께 제공됩니다.
이를 통해 반환 된 parcelable을 outState에 저장할 수 있습니다. 예 :
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
, 저장 한 상태로 복원 위치를 복원합니다. 예 :
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
모두 마무리하면 최종 코드는 다음과 같습니다.
private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";
/**
* This is a method for Fragment.
* You can do the same in onCreate or onRestoreInstanceState
*/
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(savedInstanceState != null)
{
Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}
편집 : LinearLayoutManager의 하위 클래스이기 때문에 GridLayoutManager와 동일한 api를 사용할 수도 있습니다. 제안에 대해 @wegsehen에게 감사드립니다.
편집 : 백그라운드 스레드에서도 데이터를로드하는 경우 방향 변경시 위치를 복원하려면 onPostExecute / onLoadFinished 메서드 내에서 onRestoreInstanceState를 호출해야합니다. 예 :
@Override
protected void onPostExecute(ArrayList<Movie> movies) {
mLoadingIndicator.setVisibility(View.INVISIBLE);
if (movies != null) {
showMoviePosterDataView();
mDataAdapter.setMovies(movies);
mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
} else {
showErrorMessage();
}
}
RecyclerView
하지만 당신은이없는 경우 ViewPager
A를을 RecycerView
각 페이지에.
onCreateView
않지만 데이터로드 후에는 작동합니다. 여기에서 @Farmaker 답변을 참조하십시오.
저장
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
복원
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
그래도 작동하지 않으면
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
저장 onPause()
및 복원onResume()
lastFirstVisiblePosition
에 0
그것을 복원 한 후. 이 사례는 RecyclerView
파일 탐색기 앱과 같이 하나에 다른 데이터를로드하는 조각 / 활동이있는 경우에 유용 합니다.
SwipeRefreshLayout
?
RecyclerView
내 목록 활동에서 멀리 이동할 때 Recycler View의 스크롤 위치를 저장하고 뒤로 이동하기 위해 뒤로 버튼을 클릭하고 싶었습니다. 이 문제에 대해 제공된 많은 솔루션이 필요 이상으로 복잡하거나 내 구성에서 작동하지 않으므로 솔루션을 공유 할 것이라고 생각했습니다.
먼저 많은 사람들이 보여준대로 onPause에 인스턴스 상태를 저장합니다. 이 버전의 onSaveInstanceState가 RecyclerView.LayoutManager 클래스의 메서드라는 점을 여기서 강조 할 가치가 있다고 생각합니다.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
이것이 제대로 작동하도록하는 핵심은 어댑터를 연결 한 후 onRestoreInstanceState를 호출하는 것입니다. 일부는 다른 스레드에 표시되어 있습니다. 그러나 실제 메서드 호출은 많은 사람들이 표시 한 것보다 훨씬 간단합니다.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
onCreate ()에 변수를 설정하고 onPause ()에 스크롤 위치를 저장하고 onResume ()에 스크롤 위치를 설정합니다.
public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;
@Override
public void onCreate(Bundle savedInstanceState)
{
//Set Variables
super.onCreate(savedInstanceState);
cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
mLayoutManager = new LinearLayoutManager(this);
cRecyclerView.setHasFixedSize(true);
cRecyclerView.setLayoutManager(mLayoutManager);
}
@Override
public void onPause()
{
super.onPause();
//read current recyclerview position
index = mLayoutManager.findFirstVisibleItemPosition();
View v = cRecyclerView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}
@Override
public void onResume()
{
super.onResume();
//set recyclerview position
if(index != -1)
{
mLayoutManager.scrollToPositionWithOffset( index, top);
}
}
더 이상 스스로 상태를 저장하고 복원 할 필요가 없습니다. xml에서 고유 ID를 설정하고 recyclerView.setSaveEnabled (true) (기본값은 true) 시스템이 자동으로 수행합니다. 이에 대한 자세한 내용은 http://trickyandroid.com/saving-android-view-state-correctly/입니다.
지원 라이브러리에 번들로 제공되는 모든 레이아웃 관리자는 스크롤 위치를 저장하고 복원하는 방법을 이미 알고 있습니다.
스크롤 위치는 다음 조건이 충족 될 때 발생하는 첫 번째 레이아웃 단계에서 복원됩니다.
RecyclerView
RecyclerView
일반적으로 레이아웃 관리자를 XML로 설정하므로 지금해야 할 일은
RecyclerView
이 작업은 언제든지 수행 할 수 있으며 Fragment.onCreateView
또는로 제한되지 않습니다 Activity.onCreate
.
예 : 데이터가 업데이트 될 때마다 어댑터가 연결되어 있는지 확인하십시오.
viewModel.liveData.observe(this) {
// Load adapter.
adapter.data = it
if (list.adapter != adapter) {
// Only set the adapter if we didn't do it already.
list.adapter = adapter
}
}
저장된 스크롤 위치는 다음 데이터에서 계산됩니다.
따라서 항목의 크기가 세로 및 가로 방향이 다른 경우 약간의 불일치를 예상 할 수 있습니다.
는 RecyclerView
/ ScrollView
/ 가질 필요가 어떤 android:id
상태에 저장 될 수 있습니다.
LinearLayoutManager.SavedState
. cs.android.com/androidx/platform/frameworks/support/+/…
여기에 그것들을 저장하고 조각에서 recyclerview의 상태를 유지하는 방법이 있습니다. 먼저 아래 코드와 같이 상위 활동에 구성 변경 사항을 추가하십시오.
android:configChanges="orientation|screenSize"
그런 다음 Fragment에서 이러한 인스턴스 메서드의
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
@onPause 메서드에 아래 코드를 추가하십시오.
@Override
public void onPause() {
super.onPause();
//This used to store the state of recycler view
mBundleRecyclerViewState = new Bundle();
mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}
아래와 같이 onConfigartionChanged Method를 추가합니다.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//When orientation is changed then grid column count is also changed so get every time
Log.e(TAG, "onConfigurationChanged: " );
if (mBundleRecyclerViewState != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
}
}, 50);
}
mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}
이 접근 방식은 문제를 다시 해결할 것입니다. 다른 레이아웃 관리자가 있으면 상위 레이아웃 관리자를 교체하십시오.
AsyncTaskLoader를 사용하여 인터넷에서 데이터를 다시로드해야 할 때 회전 후 GridLayoutManager로 RecyclerView 위치를 복원하는 방법입니다.
Parcelable 및 GridLayoutManager의 전역 변수와 정적 최종 문자열을 만듭니다.
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
onSaveInstance ()에 gridLayoutManager 상태 저장
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
onRestoreInstanceState에서 복원
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//restore recycler view at same position
if (savedInstanceState != null) {
savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
}
그런 다음 로더가 인터넷에서 데이터를 가져올 때 onLoadFinished ()에서 recyclerview 위치를 복원합니다.
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. 물론 onCreate 내에서 gridLayoutManager를 인스턴스화해야합니다. 건배
동일한 요구 사항이 있었지만 여기의 솔루션은 recyclerView의 데이터 소스로 인해 선을 넘지 못했습니다.
onPause ()에서 RecyclerViews의 LinearLayoutManager 상태를 추출했습니다.
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
은에 저장되고에서 onSaveInstanceState(Bundle outState)
다시 추출 onCreate(Bundle savedInstanceState)
됩니다 savedInstanceState != null
.
그러나 recyclerView 어댑터는 Room 데이터베이스에 대한 DAO 호출에 의해 반환 된 ViewModel LiveData 개체에 의해 채워지고 업데이트됩니다.
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
결국 어댑터에 데이터가 설정된 직후 인스턴스 상태를 복원하면 회전에 걸쳐 스크롤 상태가 유지된다는 것을 알게되었습니다.
LinearLayoutManager
데이터베이스 호출에 의해 데이터가 반환 될 때 복원 한 상태를 덮어 쓰거나 복원 된 상태가 '빈'LinearLayoutManager에 대해 무의미 했기 때문 이라고 생각합니다.
어댑터 데이터를 직접 사용할 수있는 경우 (즉, 데이터베이스 호출에서 연속적이지 않은 경우), 어댑터가 recyclerView에 설정된 후에 LinearLayoutManager에서 인스턴스 상태를 복원 할 수 있습니다.
두 시나리오의 차이는 오랫동안 나를 붙 잡았습니다.
Android API 레벨 28에서는 제 LinearLayoutManager
및 RecyclerView.Adapter
제 Fragment#onCreateView
방법과 Just Worked ™ ️ 모든 것을 설정했는지 확인합니다 . 나는 어떤 할 필요가 없었 onSaveInstanceState
거나 onRestoreInstanceState
일을.
Eugen Pechanec의 답변은 이것이 작동하는 이유를 설명합니다.
androidx recyclerView 라이브러리의 버전 1.2.0-alpha02부터 이제 자동으로 관리됩니다. 다음과 같이 추가하십시오.
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
그리고 사용 :
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
StateRestorationPolicy 열거 형에는 3 가지 옵션이 있습니다.
이 답변 당시 recyclerView 라이브러리는 여전히 alpha03이지만 알파 단계는 프로덕션 목적에 적합하지 않습니다 .
나에게 문제는 어댑터를 변경할 때마다 새 레이아웃 관리자를 설정하여 recyclerView에서 스크롤 위치를 잃었다는 것입니다.
RecyclerView에 대한 최근의 곤경을 공유하고 싶습니다. 나는 같은 문제를 겪고있는 모든 사람이 도움이되기를 바랍니다.
내 프로젝트 요구 사항 : 그래서 내 주요 활동 (Activity-A)에서 클릭 가능한 항목을 나열하는 RecyclerView가 있습니다. 항목을 클릭하면 항목 세부 정보 (활동 -B)와 함께 새 활동이 표시됩니다.
매니페스트 파일에서 Activity-A가 Activity-B의 부모라는 것을 구현했습니다. 그런 식으로 Activity-B의 ActionBar에 뒤로 또는 홈 버튼이 있습니다.
문제 : Activity-B ActionBar에서 뒤로 또는 홈 버튼을 누를 때마다 Activity-A가 onCreate ()에서 시작하는 전체 활동 수명 주기로 들어갑니다.
RecyclerView의 어댑터 목록을 저장하는 onSaveInstanceState ()를 구현했지만 Activity-A가 수명주기를 시작할 때 onCreate () 메서드에서 saveInstanceState는 항상 null입니다.
인터넷을 더 파헤쳐 보면 동일한 문제가 발생했지만 Anroid 장치 아래의 뒤로 또는 홈 버튼 (기본 뒤로 / 홈 버튼)이 활동 A가 활동 라이프 사이클에 들어 가지 않는다는 것을 알게되었습니다.
해결책:
Activity-B에서 홈 또는 뒤로 버튼을 활성화했습니다.
onCreate () 메서드 아래에 supportActionBar? .setDisplayHomeAsUpEnabled (true) 줄을 추가합니다.
overide fun onOptionsItemSelected () 메서드에서 id를 기반으로 항목을 클릭 한 item.ItemId를 확인했습니다. 뒤로 버튼의 ID는
android.R.id.home
그런 다음 android.R.id.home 내부에서 finish () 함수 호출을 구현합니다.
이것은 활동 B를 끝내고 전체 수명주기를 거치지 않고 Acitivy-A를 가져올 것입니다.
내 요구 사항에 대해 이것이 지금까지 최고의 솔루션입니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_show_project)
supportActionBar?.title = projectName
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId){
android.R.id.home -> {
finish()
}
}
return super.onOptionsItemSelected(item)
}
나는 약간의 차이로 동일한 문제가 있었고 Databinding 을 사용 하고 있었고 바인딩 메서드에서 recyclerView 어댑터와 layoutManager를 설정했습니다.
문제는 onResume 후에 데이터 바인딩이 발생 했기 때문에 onResume 후에 내 layoutManager를 재설정하고 매번 원래 위치로 스크롤한다는 의미입니다. 따라서 Databinding 어댑터 메서드에서 layoutManager 설정을 중지하면 다시 정상적으로 작동합니다.
Activity.java :
public RecyclerView.LayoutManager mLayoutManager;
Parcelable state;
mLayoutManager = new LinearLayoutManager(this);
// Inside `onCreate()` lifecycle method, put the below code :
if(state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
@Override
protected void onResume() {
super.onResume();
if (state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
}
@Override
protected void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
왜 내가 사용하고 있습니다 OnSaveInstanceState()
에서 onPause()
다른 활동으로 전환이 onPause가 called.It 그 스크롤 위치를 저장하고 우리가 다른 활동에서 오는 위치를 복원 할 것이지만, 수단.