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


96

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

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

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

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

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

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


RecyclerView 또는 LayoutManager의 너비 및 높이 값에 관한 것입니다. 이 솔루션을 확인 stackoverflow.com/questions/28993640/...
— serefakyuz

@Konard, 제 경우에는 Seekbar 구현 및 다음 문제로 실행되는 오디오 파일 목록이 있습니다. 1) 몇 개의 마지막 색인 항목에 대해 notifyItemChanged (pos)를 트리거하자마자 자동으로 하단에서 뷰를 푸시합니다. 2) notifyItemChanged (pos)를 유지하는 동안 목록이 멈춰서 쉽게 스크롤 할 수 없습니다. 질문으로 물어 보 겠지만 이러한 문제를 해결하는 더 좋은 방법이 있으면 알려주세요.
— CoDe

답변:


139

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

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

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

더 간단합니다. 도움이되기를 바랍니다.


1
이것은 실제로 정답입니다. 비동기 데이터 로딩 때문에 까다로워 지지만 복원을 연기 할 수 있습니다.
— EpicPandaForce

내가 찾던 정확히이 완벽
— Adeel 터크

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m 이 구현을 시도하고 있지만 작동하지 않습니다. 뭘 잘못하고 있습니까?
— Kaustubh Bhagwat

@KaustubhBhagwat 아마도 사용하기 전에 "recyclerViewState! = null"을 확인해야 할 것입니다.
— DawnYu

4
이 코드는 정확히 어디에 사용해야합니까? onResume 메서드에서?
— Lucky_girl

47

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

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

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

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

2
나도 .notifyDataSetChanged ()로 작동 adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); 시도
— 마니

4
이것은 제 경우에 도움이되었습니다. 위치 0에 여러 항목을 삽입 notifyDataSetChanged하고 스크롤 위치가 변경되어 새 항목이 표시됩니다. 그러나 사용하는 경우 notifyItemRangeInserted(0, newItems.size() - 1)는 RecyclerView스크롤 상태를 유지합니다.
— Carlosph

아주 좋은 대답, 나는 그 대답의 해결책을 하루 종일 검색합니다. .. 너무 많이, 감사합니다
— Gundu Bandgar에게

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); 최신 Google I / O (recyclerview in 및 outs에 대한 프레젠테이션보기)에 표시된대로 피해야합니다. 일반적인 범용 새로 고침 대신 recyclerview에 정확히 어떤 항목이 변경되었는지 알려주는 것이 좋습니다 (지식이있는 경우). 모든 견해의.
— A. Steenbergen

3
가기는 작동하지 않는, recyclerview 상쾌 내가 추가에도 불구하고
— Shaahul

21

편집 : 에서와 같이 똑같은 명백한 위치 를 복원하려면 에서와 같이 똑같이 보이게하려면 약간 다른 작업을 수행해야합니다 (정확한 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);
}

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


전체 예제를 게시 할 수 있는지 이해가 안 되나요?

이것은 실제로 내가 전체 활동을 게시하는 것을 원하지 않는 경우 얻을 수있는 전체 예제에 가깝습니다
— A. Steenbergen

나는 뭔가를 얻지 못한다. 목록의 시작 부분에 추가 된 항목이있는 경우 실제로이 영역을 차지한 다른 항목을보고 있으므로 동일한 스크롤 위치에 머 무르지 않는 것이 잘못된 것입니까?
— 안드로이드 개발자

예,이 경우 복원 할 때 위치를 조정해야하지만 일반적으로 회전하는 동안 항목을 추가하거나 제거하지 않기 때문에 이것은 문제의 일부가 아닙니다. 그것은 이상하고 행동이며 솔직히 상상할 수없는 몇 가지 특별한 경우를 제외하고는 아마도 사용자의 관심에 맞지 않을 것입니다.
— A. Steenbergen 17.11.

9

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

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

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

이제 어댑터가 null인지 아닌지 확인하고 null 일 때만 초기화하는 것을 볼 수 있습니다.

어댑터가 null이 아니면 어댑터를 한 번 이상 초기화했음을 확신합니다.

그래서 어댑터에 목록을 추가하고 notifydatasetchanged를 호출합니다.

RecyclerView는 항상 스크롤 된 마지막 위치를 유지하므로 마지막 위치를 저장할 필요가 없습니다. notifydatasetchanged를 호출하기 만하면됩니다. recycler보기는 항상 맨 위로 이동하지 않고 데이터를 새로 고칩니다.

감사합니다 Happy Coding


나는이 허용 대답 @Konrad Morawski에 갈 생각
— Jithin 유

5

@DawnYu 대답을 사용하여 다음 notifyDataSetChanged()과 같이 래핑하여 스크롤 위치를 유지하십시오 .

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

완벽한 ... 최고의 답변
— jlandyr

나는이 답변을 오랫동안 찾고있었습니다. 대단히 감사합니다.
— Alaa AbuZarifa

1

@DawnYu의 최상위 답변은 작동하지만 recyclerview는 먼저 맨 위로 스크롤 한 다음 의도 한 스크롤 위치로 돌아가서 "깜박임"반응을 일으키며 기분이 좋지 않습니다.

recyclerView를 새로 고치려면 특히 깜박임없이 다른 활동에서 온 후 스크롤 위치를 유지하려면 다음을 수행해야합니다.

  1. DiffUtil을 사용하여 리사이클 러보기를 업데이트하고 있는지 확인하십시오. 여기에서 자세히 알아보세요 : https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. 활동을 재개하거나 활동을 업데이트하려는 시점에서 데이터를 recyclerview에로드합니다. diffUtil을 사용하면 위치를 유지하면서 recyclerview에서 업데이트 만 수행됩니다.

도움이 되었기를 바랍니다.


0

Recyclerview를 사용하지 않았지만 ListView에서 사용했습니다. Recyclerview의 샘플 코드 :

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

사용자가 스크롤 할 때 리스너입니다. 성능 오버 헤드는 중요하지 않습니다. 그리고 첫 번째로 보이는 위치는 이런 방식으로 정확합니다.


좋아, findFirstVisibleItemPosition"첫 번째 보이는 뷰의 어댑터 위치"를 반환하지만 오프셋은 어떻습니까?
— Konrad Morawski

1
Recyclerview에서 보이는 위치에 대해 ListView의 setSelection 메서드와 유사한 방법이 있습니까? 이것이 내가 ListView를 위해 한 일입니다.
— 원본 Android

1
저장할 값인 dy 매개 변수와 함께 setScrollY 메소드를 사용하는 것은 어떻습니까? 작동하면 답변을 업데이트하겠습니다. Recyclerview를 사용하지 않았지만 향후 앱에서 사용하고 싶습니다.
— 원본 Android

스크롤 위치를 저장하는 방법에 대한 내 대답을 참조하십시오.
— A. Steenbergen 2015 년

1
좋아요. 다음에 명심 할게요. 악의로 당신을 비하하지 않았어요. 그러나 저는 짧고 불완전한 답변에 동의하지 않습니다. 다른 개발자가 많은 배경 정보를 가정하는 짧은 답변을 시작했을 때 실제로 도움이되지 않았기 때문입니다. 왜냐하면 너무 불완전하고 명확한 질문을 많이해야했기 때문입니다. 일반적으로 오래된 stackoverflow 게시물에서는 할 수 없습니다. 또한 Konrad가 답변을 수락하지 않았는지 확인하십시오. 이는 이러한 답변으로 문제가 해결되지 않았 음을 나타냅니다. 나는 당신의 일반적인 요점을 보지만.
— A. Steenbergen 2015 년

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

여기서 해결책은 새 메시지가 올 때 recyclerview를 계속 스크롤하는 것입니다.

onChanged () 메서드는 recyclerview에서 수행되는 작업을 감지합니다.


-1

Kotlin에서 저에게 효과적입니다.

  1. 어댑터를 만들고 생성자에서 데이터를 전달합니다.
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. 이 속성을 변경하고 notifyDataSetChanged () 호출
adapter.currentPole = pole
adapter.notifyDataSetChanged()

스크롤 오프셋은 변경되지 않습니다.


-2

나는 그들이 '기한'이되고 업데이트가 필요할 때까지 각각 몇 분 안에 시간이 있었던 항목 목록 에이 문제가있었습니다. 데이터를 업데이트 한 다음

orderAdapter.notifyDataSetChanged();

매번 맨 위로 스크롤됩니다. 나는 그것을

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

그리고 괜찮 았습니다. 이 스레드의 다른 방법은 나를 위해 일하지 않았습니다. 하지만이 방법을 사용하면 업데이트 될 때 각 개별 항목이 깜박이므로 부모 조각의 onCreateView에 넣어야했습니다.

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

-2

recyclerview 항목 내에 하나 이상의 EditText가있는 경우 이러한 항목의 자동 초점을 비활성화하고이 구성 을 recyclerview 의 상위 보기에 넣습니다 .

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

recyclerview 항목에서 다른 활동을 시작했을 때이 문제가 발생했습니다. 다시 돌아와서 RV가 이동하는 notifyItemChanged (position)로 한 항목의 한 필드에 대한 업데이트를 설정했을 때 내 결론은 EditText의 자동 초점이라는 것입니다. 항목, 위의 코드가 내 문제를 해결했습니다.

베스트.


-3

oldPosition과 위치가 같으면 반환하십시오.

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.