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.