Оновлення даних у RecyclerView та збереження положення прокрутки


96

Як можна оновлювати відображені дані RecyclerView(закликаючи notifyDataSetChangedадаптер) і переконатися, що положення прокрутки було скинуто саме там, де воно було?

У випадку хорошого оль ' 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 (поз), він натискає перегляд знизу автоматичним. 2) Хоча продовжувати notifyItemChanged (поз), він застряг у списку і не дозволяє легко прокручувати. Хоча я буду задавати це питання, але повідомте, будь ласка, якщо у вас є кращий підхід до вирішення такої проблеми.
CoDe

Відповіді:


139

Я використовую цей. ^ _ ^

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

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

Простіше, сподіваємось, це допоможе тобі!


1
Це насправді правильна відповідь imo. Це стає складним лише через асинхронну завантаження даних, але ви можете відкласти відновлення.
EpicPandaForce

ідеально +2 саме те, що я шукав
Adeel Turk

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m Я намагаюся реалізувати це, але це не працює, що я роблю неправильно?
Каустубх Багват

@KaustubhBhagwat Можливо, вам слід перевірити "reciclerViewState! = Null" перед використанням.
DawnYu

4
Де саме я повинен використовувати цей код? у методі onResume?
Lucky_girl

47

У мене досить схожа проблема. І я придумав таке рішення.

Використання notifyDataSetChanged- погана ідея. Ви повинні бути більш конкретними, тоді RecyclerViewзбережете стан прокрутки для вас.

Наприклад, якщо вам потрібно лише оновити або іншими словами, ви хочете, щоб кожен вид переглянуто, просто зробіть це:

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

2
Я спробував adapter.notifyItemRangeChanged (0, adapter.getItemCount ());, який теж працює як .notifyDataSetChanged ()
Мані

4
Це допомогло в моєму випадку. Я вставляв кілька позицій у позиції 0, а notifyDataSetChangedположення прокрутки змінювалося б, показуючи нові елементи. Однак, якщо використовувати зберігає стан прокрутки. notifyItemRangeInserted(0, newItems.size() - 1)RecyclerView
Карлосф

дуже гарна відповідь, я шукаю рішення цієї відповіді цілий один день. дякую, так багато ..
Гунду Бандгар

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); слід уникати, як зазначено в останньому введенні / виведенні Google (перегляньте презентацію про вхід-вихід reciclerview), краще (якщо у вас є знання) повідомити reciclerview, які саме елементи замінено замість простого загального оновлення усіх поглядів.
A. Steenbergen

3
його не працює, рециркулятор оновлення доверху, хоча я додав
Шаахул

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

Це повертає список до його точного видимого положення. Мабуть, тому що це буде виглядати однаково для користувача, але воно не матиме однакового значення прокруткиY (через можливі відмінності в розмірах ландшафту / портрета).

Зверніть увагу, що це працює лише з LinearLayoutManager.

--- Нижче, як відновити точну прокрутку Y, що, ймовірно, призведе до того, що список виглядатиме по-іншому ---

  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. Збережіть цю змінну у своєму пакеті та відновіть її у відновленні стану до іншої змінної , ми будемо називати її 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);
}

Збереження стану у фрагментах працює подібним чином, але для фактичного збереження стану потрібно трохи додаткової роботи, але є багато статей, що займаються цим, тому у вас не повинно виникнути проблем із з’ясуванням принципів збереження прокруткиY і відновлення його залишається незмінним.


я не розумію, ви можете розмістити повний приклад?

Це насправді наближений до повного прикладу, який ви можете отримати, якщо ви не хочете, щоб я публікував всю свою діяльність
А. Стінберген

Я щось не отримую. Якщо на початку списку були додані елементи, чи не залишатись на одній позиції прокрутки помилкою, оскільки ви зараз справді будете шукати різні елементи, які зайняли цю область?
андроїд розробник

Так, у такому випадку вам доведеться відрегулювати положення під час відновлення, однак, це не є частиною питання, оскільки зазвичай під час обертання ви не додаєте та не видаляєте елементи. Це було б дивно і поведінка, і, мабуть, не в інтересах користувача, за винятком деяких особливих випадків, які можуть існувати, яких я не можу уявити, чесно.
A. Steenbergen

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, тоді я запевняю, що я ініціалізував свій адаптер принаймні один раз.

Тому я просто додаю список до адаптера та викликаю notifydatasetchanged.

RecyclerView завжди тримає останню прокручену позицію, тому вам не потрібно зберігати останню позицію, просто зателефонуйте notifydatasetchanged, подання рециклера завжди оновлює дані, не переходячи вгору.

Дякуємо Happy Coding


Я думаю, що це переходить до прийнятої відповіді @Konrad Morawski
Jithin Jude

5

Зберігайте позицію прокрутки, використовуючи @DawnYu відповідь, щоб завернути notifyDataSetChanged()так:

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

ідеально ... найкраща відповідь
jlandyr

Я довго шукав цю відповідь. дуже тобі дякую.
Alaa AbuZarifa

1

Верхня відповідь від @DawnYu працює, але рециркулятор спочатку прокрутить до верху, а потім повернеться до потрібного положення прокрутки, викликаючи реакцію "мерехтіння", яка не є приємною.

Щоб оновити reciclerView, особливо після появи іншої діяльності, без мерехтіння та збереження положення прокрутки, потрібно виконати наступне.

  1. Переконайтеся, що ви оновлюєте вигляд утилізатора за допомогою DiffUtil. Детальніше про це читайте тут: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Повторно продовжуючи свою діяльність, або в той момент, коли ви хочете оновити свою діяльність, завантажте дані до свого переглядача. Використовуючи diffUtil, лише оновлення будуть виконуватися в перегляді утилізатора, зберігаючи його положення.

Сподіваюся, це допомагає.


0

Я не використовував Recyclerview, але зробив це на ListView. Зразок коду в Recyclerview:

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

Це слухач, коли користувач прокручує. Накладні витрати не є суттєвими. І перша видима позиція є точною таким чином.


Гаразд, findFirstVisibleItemPositionповертає "перехідне положення першого видимого виду", а як щодо зміщення.
Конрад Моравський,

1
Чи існує аналогічний метод Recyclerview для методу setSelection ListView для видимого положення? Це те, що я зробив для ListView.
Оригінальний Android

1
Як щодо використання методу setScrollY з параметром dy, значення якого ви збережете? Якщо це спрацює, я оновлю свою відповідь. Я не використовував Recyclerview, але хочу в майбутньому додатку.
Оригінальний Android

Будь ласка, перегляньте мою відповідь нижче про те, як зберегти позицію прокрутки.
А. Стінберген

1
Добре, я буду пам’ятати про це наступного разу, я не проголосував за вас з недоброзичливістю. Однак я не погоджуюся з короткими та неповними відповідями, тому що коли я почав робити короткі відповіді, де інші розробники припускали, що багато відомостей про фонову інформацію мені не дуже допомогло, тому що вони часто були занадто неповними, і мені довелося задавати багато уточнюючих питань, щось ти як правило, не можна робити на старих повідомленнях stackoverflow. Також зверніть увагу, як Конрад не прийняв відповіді, що вказує на те, що ці відповіді не вирішили його проблеми. Хоча я бачу вашу загальну думку.
А. Стінберген

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

Рішення тут полягає в тому, щоб продовжувати прокручувати перегляд reciler при появі нового повідомлення.

Метод onChanged () виявляє дію, що виконується на перегляді рециркулятора.


-1

Це працює для мене в Котліні.

  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

Якщо у вас є один або кілька EditTexts всередині елементів перегляду recyclerview, вимкніть їх автофокус, помістивши цю конфігурацію в батьківський подання recyclerview:

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

Цю проблему у мене виникло, коли я розпочав іншу діяльність, запущену з елемента рециркуляції, коли я повернувся і встановив оновлення одного поля в одному елементі з notifyItemChanged (позиція) прокрутки RV рухається, і мій висновок полягав у тому, що автофокусування EditText Елементи, код вище вирішив мою проблему.

найкращий.


-3

Просто поверніться, якщо стареПоложення та позиція однакові;

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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.