RecyclerView'da verileri yenileme ve kaydırma konumunu koruma


98

İçinde görüntülenen veriler nasıl yenilenir RecyclerView( notifyDataSetChangedadaptörünü çağırarak ) ve kaydırma konumunun tam olarak olduğu yere sıfırlandığından emin olunur?

İyi bir durum olması durumunda, ListViewgereken tek şey geri almak getChildAt(0), kontrol etmek getTop()ve setSelectionFromTopdaha sonra aynı kesin verilerle aramaktır.

Olması durumunda mümkün görünmüyor RecyclerView.

Sanırım LayoutManagergerçekten sağlayanını kullanmam gerekiyor scrollToPositionWithOffset(int position, int offset), ama konumu ve ofseti almanın doğru yolu nedir?

layoutManager.findFirstVisibleItemPosition()ve layoutManager.getChildAt(0).getTop()?

Yoksa işi halletmenin daha zarif bir yolu var mı?


RecyclerView veya LayoutManager'ınızın genişlik ve yükseklik değerleri ile ilgilidir. Bu çözümü kontrol edin: stackoverflow.com/questions/28993640/…
serefakyuz

@Konard, Benim durumumda Seekbar uygulamasına sahip ve aşağıdaki sorunla çalışan ses dosyası listem var: 1) NotifyItemChanged (pos) tetiklediği anda son indekslenen birkaç öğe için altta otomatik olarak görünümü itin. 2) notifyItemChanged (konum) üzerinde devam ederken listeyi sıkıştı ve kolayca kaydırılmasına izin vermiyordu. Yine de bir soru soracağım ancak bu sorunu çözmek için daha iyi bir yaklaşımınız varsa lütfen bize bildirin.
CoDe

Yanıtlar:


145

Bunu kullanıyorum. ^ _ ^

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

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

Daha basit, umarım size yardımcı olur!


1
Bu aslında doğru cevap imo. Yalnızca eşzamansız veri yüklemesi nedeniyle zorlaşır, ancak geri yüklemeyi erteleyebilirsiniz.
EpicPandaForce

mükemmel +2 tam olarak aradığım şey
Adeel Turk

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m Bunu uygulamaya çalışıyorum ama çalışmıyor, neyi yanlış yapıyorum?
Kaustubh Bhagwat

@KaustubhBhagwat Belki kullanmadan önce "recyclerViewState! = Null" öğesini kontrol etmelisiniz.
DawnYu

4
Bu kodu tam olarak nerede kullanmalıyım? onResume yönteminde?
Lucky_girl

47

Oldukça benzer bir problemim var. Ve aşağıdaki çözümü buldum.

Kullanmak notifyDataSetChangedkötü bir fikir. Daha spesifik olmalısınız, sonra RecyclerViewkaydırma durumunu sizin için kaydedecektir.

Örneğin, yalnızca yenilemeniz gerekiyorsa veya başka bir deyişle, her görünümün yeniden bağlanmasını istiyorsanız, şunu yapın:

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

2
Adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); denedim, bu da .notifyDataSetChanged () olarak çalışıyor
Mani

4
Bu benim durumumda yardımcı oldu. 0 konumuna birkaç öğe ekliyordum ve notifyDataSetChangedkaydırma konumu yeni öğeleri göstererek değişecekti. Ancak kullanırsanız kaydırma durumunu tutar. notifyItemRangeInserted(0, newItems.size() - 1)RecyclerView
Carlosph

çok çok güzel cevap, bütün bir gün o cevabın çözümünü arıyorum. çok teşekkür ederim ..
Gundu Bandgar

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); en son Google G / Ç'sinde belirtildiği gibi kaçınılmalıdır (geri dönüştürücünün giriş ve çıkışları hakkındaki sunumu izleyin), geri dönüşümcüye düz bir tümünü yakalama yenilemesi yerine tam olarak hangi öğelerin değiştiğini söylemek (bilgiye sahipseniz) daha iyi olur tüm görünümlerin.
A. Steenbergen


21

DÜZENLEME: İçinde olduğu gibi tam olarak aynı görünen konumu geri yüklemek için, tam olarak olduğu gibi görünmesini sağlamak için, biraz farklı bir şey yapmamız gerekir (tam scrollY değerini nasıl geri yükleyeceğinizi aşağıya bakın):

Konumu ve ofseti şu şekilde kaydedin:

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);

Ve sonra parşömeni şu şekilde geri yükleyin:

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

Bu, listeyi tam görünen konumuna geri yükler . Görünüşe göre kullanıcıya aynı görünecek, ancak aynı kaydırma değerine sahip olmayacak (yatay / dikey düzen boyutlarındaki olası farklılıklar nedeniyle).

Bunun yalnızca LinearLayoutManager ile çalıştığını unutmayın.

--- ScrollY'nin tam olarak nasıl geri yükleneceğinin altında, bu muhtemelen listeyi farklı gösterecektir ---

  1. Şöyle bir OnScrollListener uygulayın:

    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;
        }
    };
    

Bu, tam kaydırma konumunu her zaman mScrollY'de saklayacaktır.

  1. Bu değişkeni Bundle'ınızda saklayın ve onu durum geri yüklemesinde farklı bir değişkene geri yükleyin , biz ona mStateScrollY adını vereceğiz.

  2. Durum geri yüklemesinden sonra ve RecyclerView'unuz tüm verilerini sıfırladıktan sonra kaydırmayı şununla sıfırlayın:

    mRecyclerView.scrollBy(0, mStateScrollY);
    

Bu kadar.

Kaydırmayı farklı bir değişkene geri yüklediğinizden emin olun, bu önemlidir, çünkü OnScrollListener .scrollBy () ile çağrılacak ve daha sonra mScrollY'yi mStateScrollY'de depolanan değere ayarlayacaktır. Bunu yapmazsanız, mScrollY iki kat kaydırma değerine sahip olacaktır (çünkü OnScrollListener, mutlak kaydırmalarla değil deltalarla çalışır).

Faaliyetlerde durum tasarrufu şu şekilde sağlanabilir:

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

Ve geri yüklemek için bunu onCreate () içinde arayın:

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

Parçalarda durum tasarrufu benzer şekilde çalışır, ancak gerçek durum tasarrufu biraz fazladan çalışma gerektirir, ancak bununla ilgili birçok makale vardır, bu nedenle parşömeni kaydetme ilkelerini bulmakta sorun yaşamazsınız. ve onu geri yüklemek aynı kalır.


anlamıyorum tam örnek gönderebilir misin?

Bu aslında tüm aktivitemi yayınlamamı istemiyorsan alabileceğin kadar tam bir örneğe yakın
A.Steenbergen

Ben bir şey anlamadım. Listenin başına eklenen öğeler varsa, aynı kaydırma konumunda kalmak yanlış olmaz mı, çünkü şimdi bu alanı ele geçiren farklı öğelere bakıyor olacaksınız?
android geliştiricisi

Evet, bu durumda geri yükleme sırasında konumu ayarlamanız gerekir, ancak bu sorunun bir parçası değildir, çünkü genellikle döndürürken öğe ekleme veya çıkarma yapmazsınız. Bu garip ve davranış olurdu ve dürüstçe hayal edemediğim bazı özel durumlar dışında muhtemelen kullanıcının çıkarına değildir.
A. Steenbergen

9

Evet, adaptör yapıcısını yalnızca bir kez yaparak bu sorunu çözebilirsiniz, burada kodlama bölümünü açıklıyorum:

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

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

Şimdi bağdaştırıcının boş olup olmadığını kontrol ettiğimi ve yalnızca boş olduğunda başlattığımı görebilirsiniz.

Adaptör boş değilse, adaptörümü en az bir kez başlattığımdan eminim.

Bu yüzden sadece adaptöre liste ekleyeceğim ve notifydatasetchanged'i çağıracağım.

RecyclerView her zaman son konumu kaydırılmış olarak tutar, bu nedenle son konumu kaydetmeniz gerekmez, sadece notifydatasetchanged çağrısı yapın, recycler görünümü her zaman en üste gitmeden verileri yeniler.

Teşekkürler Mutlu Kodlama


Sanırım bu kabul edilen cevaba gidiyor @Konrad Morawski
Jithin Jude

6

Aşağıdaki notifyDataSetChanged()gibi kaydırmak için @DawnYu yanıtını kullanarak kaydırma konumunu koruyun :

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

mükemmel ... en iyi cevap
jlandyr

Bu cevabı uzun zamandır arıyordum. çok teşekkür ederim.
Alaa AbuZarifa

2

@DawnYu'nun en iyi yanıtı işe yarıyor, ancak geri dönüşümcü görünümü önce en üste kaydıracak, ardından istenen kaydırma konumuna geri dönerek hoş olmayan bir "titreme benzeri" tepkiye neden olacak.

RecyclerView'ı, özellikle başka bir etkinlikten geldikten sonra, titremeden ve kaydırma konumunu korumadan yenilemek için aşağıdakileri yapmanız gerekir.

  1. DiffUtil kullanarak geri dönüşümcü görünümünü güncellediğinizden emin olun. Bununla ilgili daha fazlasını burada okuyun: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Aktivitenizi devam ettirdiğinizde veya aktivitenizi güncellemek istediğiniz noktada verileri geri dönüşüm görünümünüze yükleyin. DiffUtil kullanarak, geri dönüştürücünün konumunu korurken geri dönüşüm görünümünde yalnızca güncellemeler yapılacaktır.

Bu yardımcı olur umarım.


1

Recyclerview kullanmadım ama ListView üzerinde yaptım. Recyclerview'daki örnek kod:

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

Kullanıcı gezinirken dinleyicidir. Performans ek yükü önemli değildir. Ve ilk görünen konum bu şekilde doğrudur.


Tamam, findFirstVisibleItemPosition"ilk görünür görünümün bağdaştırıcı konumunu" döndürür, peki ya ofset.
Konrad Morawski

1
Görünür konum için Recyclerview için ListView'ün setSelection yöntemine benzer bir yöntem var mı? ListView için yaptığım buydu.
Orijinal Android

1
Kaydedeceğiniz bir değer olan dy parametresi ile setScrollY yöntemini kullanmaya ne dersiniz? Çalışırsa cevabımı güncelleyeceğim. Recyclerview kullanmadım ama gelecekteki uygulamamda kullanmak istiyorum.
Orijinal Android

Lütfen kaydırma konumunu nasıl kaydedeceğime dair aşağıdaki cevabıma bakın.
A. Steenbergen

1
Pekala, bunu bir dahaki sefere aklımda tutacağım, seni kötü niyetle olumsuz oylamadım. Bununla birlikte, kısa ve eksik cevaplara katılmıyorum çünkü diğer geliştiricilerin çok fazla arka plan bilgisinin olduğunu varsaydığı kısa cevapları başlattığımda bana pek yardımcı olmadı, çünkü bunlar genellikle çok eksikti ve birçok açıklayıcı soru sormak zorunda kaldım. genellikle eski stackoverflow postlarında yapılamaz. Ayrıca Konrad tarafından hiçbir cevabın kabul edilmediğine dikkat edin, bu da bu cevapların problemini çözmediğini gösterir. Genel bakış açınızı görmeme rağmen.
A. Steenbergen

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

Buradaki çözüm, yeni mesaj geldiğinde geri dönüşüm görünümünü kaydırmaya devam etmektir.

OnChanged () yöntemi, recyclerview üzerinde gerçekleştirilen eylemi algılar.


-1

Bu benim için Kotlin'de çalışıyor.

  1. Bağdaştırıcıyı oluşturun ve yapıcıda verilerinizi verin
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. bu özelliği değiştirin ve notifyDataSetChanged () öğesini çağırın
adapter.currentPole = pole
adapter.notifyDataSetChanged()

Kaydırma uzaklığı değişmez.


-2

Bu problemi, her biri dakikalar içinde 'zamanı gelene kadar' ve güncellenmesi gereken öğelerin listesiyle yaşadım. Verileri günceller ve sonra ararım

orderAdapter.notifyDataSetChanged();

ve her seferinde en üste kayardı. Ben onu değiştirdim

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

ve iyiydi. Bu konudaki diğer yöntemlerin hiçbiri benim için işe yaramadı. Bu yöntemi kullanırken, her bir öğenin güncellendiğinde yanıp sönmesini sağladı, bu yüzden bunu ana parçanın onCreateView'una da koymak zorunda kaldım.

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

-2

Bir geri dönüşüm görüntüleme öğesinin içinde bir veya daha fazla EditTexts varsa, bunların otomatik odaklamasını devre dışı bırakarak bu yapılandırmayı geri dönüşüm görünümünün üst görünümüne koyun:

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

Geri dönüşümlü bir öğeden başlatılan başka bir etkinliği başlattığımda, geri döndüğümde ve RV hareketlerinin kaydırmasını notifyItemChanged (konum) ile bir öğedeki bir alanın güncellemesini ayarladığımda bu sorunu yaşadım ve sonucum şu oldu: EditText'in otomatik odağı Öğeler, yukarıdaki kod sorunumu çözdü.

en iyi.


-3

Sadece eskiPozisyon ve pozisyon aynıysa geri dönün;

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.