developer tip

RecyclerView에서 데이터 새로 고침 및 스크롤 위치 유지

copycodes 2020. 10. 18. 18:30
반응형

RecyclerView에서 데이터 새로 고침 및 스크롤 위치 유지


RecyclerView( notifyDataSetChanged어댑터 호출) 표시된 데이터를 어떻게 새로 고치고 스크롤 위치가 정확히 원래 위치로 재설정되는지 확인하는 방법은 무엇입니까?

좋은 ol '의 경우 ListView검색 getChildAt(0), 확인 getTop()setSelectionFromTop나중에 동일한 정확한 데이터로 호출 하는 것입니다.

의 경우 불가능한 것 같습니다 RecyclerView.

나는 LayoutManager실제로 제공하는 것을 사용해야한다고 생각 scrollToPositionWithOffset(int position, int offset)하지만 위치와 오프셋을 검색하는 적절한 방법은 무엇입니까?

layoutManager.findFirstVisibleItemPosition()그리고 layoutManager.getChildAt(0).getTop()?

아니면 일을 끝내는 더 우아한 방법이 있습니까?


나는 이것을 사용합니다. ^ _ ^

// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();

// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);

더 간단합니다. 도움이 되길 바랍니다!


나는 아주 비슷한 문제가 있습니다. 그리고 나는 다음과 같은 해결책을 생각해 냈습니다.

사용 notifyDataSetChanged은 나쁜 생각입니다. 더 구체적이어야합니다. 그러면 RecyclerView스크롤 상태가 저장됩니다.

예를 들어, 새로 고침 만 필요하거나 다시 말해 각보기를 리 바인드하려면 다음을 수행하십시오.

adapter.notifyItemRangeChanged(0, adapter.getItemCount());

편집 : 에서와 같이 똑같은 명백한 위치 를 복원하려면 에서와 같이 정확히 보이게하려면 약간 다른 작업을 수행해야합니다 (정확한 scrollY 값을 복원하는 방법은 아래 참조).

다음과 같이 위치와 오프셋을 저장합니다.

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();

outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);

그런 다음 다음과 같이 스크롤을 복원하십시오.

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);

이것은 정확한에있는 목록 복원 명백한 위치를. 사용자에게는 동일하게 보이지만 동일한 scrollY 값을 갖지 않기 때문에 분명합니다 (가로 / 세로 레이아웃 크기의 가능한 차이로 인해).

이것은 LinearLayoutManager에서만 작동합니다.

--- 목록이 다르게 보일 수있는 정확한 scrollY를 복원하는 방법 아래 ---

  1. 다음과 같이 OnScrollListener를 적용합니다.

    private int mScrollY;
    private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrollY += dy;
        }
    };
    

이것은 mScrollY에 항상 정확한 스크롤 위치를 저장합니다.

  1. 이 변수를 Bundle에 저장하고 상태 복원에서 다른 변수로 복원 하면 mStateScrollY라고합니다.

  2. 상태 복원 RecyclerView가 모든 데이터를 재설정 한 후 다음 같이 스크롤을 재설정합니다.

    mRecyclerView.scrollBy(0, mStateScrollY);
    

그게 다야.

스크롤을 다른 변수로 복원한다는 점에 유의하십시오. OnScrollListener가 .scrollBy ()로 호출되고 mScrollY를 mStateScrollY에 저장된 값으로 설정하기 때문에 이것은 중요합니다. 이 작업을 수행하지 않으면 mScrollY는 스크롤 값의 두 배를 갖게됩니다 (OnScrollListener는 절대 스크롤이 아닌 델타와 함께 작동하기 때문입니다).

활동의 상태 절약은 다음과 같이 달성 할 수 있습니다.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(ARGS_SCROLL_Y, mScrollY);
}

복원하려면 onCreate ()에서 다음을 호출하십시오.

if(savedState != null){
    mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}

프래그먼트의 상태 저장은 비슷한 방식으로 작동하지만 실제 상태 저장에는 약간의 추가 작업이 필요하지만이를 다루는 많은 기사가 있으므로 스크롤 저장 원리를 찾는 데 문제가 없어야합니다. 복원은 동일하게 유지됩니다.


예, 어댑터 생성자를 한 번만 만들어이 문제를 해결할 수 있습니다. 여기에서 코딩 부분을 설명하겠습니다.

if (appointmentListAdapter == null) {
        appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
        appointmentRecyclerView.setAdapter(appointmentListAdapter);

    } else {
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.notifyDataSetChanged();
    }

Now you can see I have checked the adapter is null or not and only initialize when it is null.

If adapter is not null then I am assured that I have initialized my adapter at least one time.

So I will just add list to adapter and call notifydatasetchanged.

RecyclerView always holds the last position scrolled, therefore you don't have to store last position, just call notifydatasetchanged, recycler view always refresh data without going to top.

Thanks Happy Coding


The top answer by @DawnYu works, but the recyclerview will first scroll to the top, then go back to the intended scroll position causing a "flicker like" reaction which isn't pleasant.

To refresh the recyclerView, especially after coming from another activity, without flickering, and maintaining the scroll position, you need to do the following.

  1. Ensure you are updating you recycler view using DiffUtil. Read more about that here: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Onresume of your activity, or at the point you want to update your activity, load data to your recyclerview. Using the diffUtil, only the updates will be made on the recyclerview while maintaining it position.

Hope this helps.


I have not used Recyclerview but I did it on ListView. Sample code in Recyclerview:

setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        rowPos = mLayoutManager.findFirstVisibleItemPosition();

It is the listener when user is scrolling. The performance overhead is not significant. And the first visible position is accurate this way.


 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

The solution here is to keep on scrolling recyclerview when new message comes.

The onChanged() method detects the action performed on recyclerview.


Keep scroll position by using @DawnYu answer to wrap notifyDataSetChanged() like this:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)

That's working for me in Kotlin.

  1. Create the Adapter and hand over your data in the constructor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. change this property and call notifyDataSetChanged()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

The scroll offset doesn't change.


I had this problem with a list of items which each had a time in minutes until they were 'due' and needed updating. I'd update the data and then after, call

orderAdapter.notifyDataSetChanged();

and it'd scroll to the top every time. I replaced that with

 for(int i = 0; i < orderArrayList.size(); i++){
                orderAdapter.notifyItemChanged(i);
            }

and it was fine. None of the other methods in this thread worked for me. In using this method though, it made each individual item flash when it was updated so I also had to put this in the parent fragment's onCreateView

RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
        ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }

If you have one or more EditTexts inside of a recyclerview items, disable the autofocus of these, putting this configuration in the parent view of recyclerview:

android:focusable="true"
android:focusableInTouchMode="true"

I had this issue when I started another activity launched from a recyclerview item, when I came back and set an update of one field in one item with notifyItemChanged(position) the scroll of RV moves, and my conclusion was that, the autofocus of EditText Items, the code above solved my issue.

best.


Just return if the oldPosition and position is same;

private int oldPosition = -1;

public void notifyItemSetChanged(int position, boolean hasDownloaded) {
    if (oldPosition == position) {
        return;
    }
    oldPosition = position;
    RLog.d(TAG, " notifyItemSetChanged :: " + position);
    DBMessageModel m = mMessages.get(position);
    m.setVideoHasDownloaded(hasDownloaded);
    notifyItemChanged(position, m);
}

Perhaps this is too simple, but I do this very thing by just calling

recreate();

My recycler view position is saved.

참고URL : https://stackoverflow.com/questions/28658579/refreshing-data-in-recyclerview-and-keeping-its-scroll-position

반응형