Подтвердить что ты не робот

Обновления данных RecyclerView и адаптера

Это вопрос о внутреннем поведении RecyclerView для тех, кто знает свою механику или готов копать исходный код. Идентификатор как ответ, подкрепленный ссылками на источник.

Оригинальный вопрос

(прокрутите вниз до "Другими словами, для более сфокусированного вопроса)

Мне нужно понять, как действия notify* (например, notifyItemInserted()) помещаются в очередь. Представьте, что у меня есть адаптер, поддерживаемый этим списком:

ArrayList<String> list = Arrays.asList("one", "three", "four");

Я хочу добавить значения zero и two, которые отсутствуют.

Пример 1

list.add(1, "two");
// notify the view
adapter.notifyItemInserted(1);


// Seconds later, I go on with zero
list.add(0, "zero");
// notify the view
adapter.notifyItemInserted(0);

Это довольно прямолинейно и понятно, нечего сказать.

Пример 2

Но что, если два действия очень близки друг к другу, а между ними нет макета?

list.add(1, "two");
list.add(0, "zero");

Что мне теперь делать?

adapter.notifyItemInserted(1);
adapter.notifyItemInserted(0);

Или, может быть,

adapter.notifyItemInserted(2);
adapter.notifyItemInserted(0);

? С точки зрения адаптера список сразу переключается с one, three, four на zero, one, two, three, four, поэтому второй вариант кажется более разумным.

Пример 3

list.add(0, "zero");
adapter.notifyItemInserted(0);
list.add(2, "two");
adapter.notifyItemInserted(...)

Как насчет этого сейчас? 1 или 2? Список сразу обновлялся, но я уверен, что между ними не было промежуточного макета.

Вопрос

У вас есть главная проблема, и я хочу знать, как я должен себя вести в этих ситуациях. Реальный случай заключается в том, что у меня есть несколько асинхронных задач, заканчивающихся методом insert(). Я могу выполнить их операции, но:

  • Я не хочу этого делать, если theres уже внутренняя очередь, и там, конечно,
  • Я не знаю, что произойдет, если два действия не пройдут без макета, см. пример 3.

Другими словами

Чтобы обновить утилиту, необходимо выполнить 4 действия:

  • Я фактически изменяю модель данных (например, вставляю что-то в массив подстановки)
  • Я вызываю adapter.notify*()
  • Recycler получает вызов
  • Recycler выполняет действие (например, вызывает getItem*() и onBind() на адаптере) и излагает изменение.

Его легко понять, когда theres no concurrency, и они происходят последовательно:

1. => 2. => 3. => 4. => (new update) 1. => 2. => 3. => 4. ...

Давайте посмотрим, что происходит между шагами.

  • Между 1. и 2.. Я бы сказал, что разработчик должен немедленно вызвать notify() после изменения данных. Это нормально.
  • Между 2. и 3.. Это происходит немедленно, здесь нет проблем.
  • Между 3. и 4.. Это происходит не. НАСКОЛЬКО МНЕ ИЗВЕСТНО. Поэтому вполне возможно, что новое обновление (шаги 1 и 2) происходит между шагами 3 и 4 предыдущего обновления.

Я хочу понять, что происходит в этом случае. Как мы должны себя вести? Должен ли я гарантировать, что предыдущий шаг 4 произошел, прежде чем вставлять новые вещи? Если да, то как?

4b9b3361

Ответ 1

Я думал о подобных вопросах раньше, и я решил:

  • Если я хочу вставить более одного элемента непосредственно в конец списка и хочу получить анимацию для всех, я должен:

    list.add("0");
    list.add("1");
    adapter.notifyItemRangeInserted(5, 2); // Suppose there were 5 items before so "0" has index of 5 and we want to insert 2 items.
    
  • Если я хочу вставить более одного элемента непосредственно в конец списка, но хотите получить отдельную анимацию для каждого вставленного элемента, я должен:

    list.add("0");
    list.add("1");
    adapter.notifyItemInserted(0);
    mRecyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            // before this happens, Be careful to call other notify* methods. Never call notifyDataSetChanged.
            adapter.notifyItemInserted(1); 
        }
    }, mRecyclerView.getItemAnimator().getAddDuration());
    
  • Если я хочу вставить более одного элемента в другое положение списка, аналогично 2.

Надеюсь, это поможет.

Ответ 2

Итак, давайте начнем с небольшого ввода, чтобы RecyclerView работал с уведомлением об элементах. И работает довольно просто с другим списком сохраненных элементов ViewGroup (ListView, например).

RecyclerView имеет очередь из View Items, которая уже нарисована. И не знает о каких-либо ваших обновлениях, не вызывая методы notify(...). Когда вы добавляете новые элементы и уведомляете RecyclerView, он начинает цикл для проверки всех просмотров один за другим.

RecyclerView contains and drawn next objects
View view-0 (position 0), view-1 (position 1), View-2 (position 2)

// Here is changes after updating
You added Item View view-new into (position 1) and Notify
RecyclerView starts loop to check changes
RecyclerView received unmodified view-0(position-0) and left them;
RecyclerView found new item view-new(position 1)
RecyclerView removing old item view-1(position 1)
RecyclerView drawing new item view-new(position 1)

// In RecyclerView queue in position-2 was item view-2, 
// But now we replacing previous item to this position
RecyclerView found new item view-1 (new position-2)
RecyclerView removing old item view-2(position 2)
RecyclerView drawing new item view-1(position 2)

// And again same behavior 
RecyclerView found new item view-3 (new position-3)
RecyclerView drawing new item view-1(position 2)

// And after all changes new RecyclerView would be
RecyclerView contains and drawn next objects
View view-0 (position 0), view-new (position 1) view-1 (position 2), View-2 (position 3)

Это просто основной поток рабочих уведомляющих функций, но то, что должно знать все эти действия, происходит в потоке пользовательского интерфейса, главной теме, даже вы можете вызывать обновление из Async Tasks. Ответ на него 2 Вопрос. Вы можете позвонить Notify в RecyclerView столько, сколько хотите, и убедитесь, что ваше действие будет в правильной очереди.

RecyclerView работает правильно при любом использовании, более сложные вопросы касаются работы адаптера. Прежде всего, вам нужно синхронизировать действие адаптера, например добавление удаляемых элементов, и полностью отказаться от использования индекса. Например, было бы лучше для вашего примера 3

Item firstItem = new Item(0, "zero");
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, "two");
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
//Other actions

ОБНОВЛЕНИЕ |

Относится к RecyclerView.Adapter Doc, где вы можете видеть те же функции с notifyDataSetChanged(). И где этот RecyclerView.Adapter вызывает дочерние элементы с расширениями android.database.Observable, см. Больше Об наблюдении. Доступ к этому наблюдаемому держателю синхронизируется, пока не появится элемент "Вид" в использовании релиза RecyclerView.

См. также RecyclerView из библиотеки поддержки версии 25.0 Линии 9934 - 9988;

Ответ 3

Это не должно быть проблемой, если вы делаете несколько обновлений между проходами макета. RecyclerView предназначен для обработки (и оптимизации) этого случая:

RecyclerView вводит дополнительный уровень абстракции между RecyclerView.Adapter и RecyclerView.LayoutManager, чтобы иметь возможность обнаруживать изменения набора данных в партиях во время расчета компоновки. [...] В RecyclerView есть два типа связанных с позицией методов:

  • расположение макета: позиция элемента в последнем расчете макета. Это позиция из LayoutManager's перспектива.
  • Позиция адаптера: положение элемента в адаптере. Это позиция с точки зрения адаптера.

Эти две позиции одинаковы , за исключением времени между отправкой adapter.notify * events и вычисление обновленного макета.

В вашем случае следующие шаги:

  • Вы обновляете слой данных

  • Вы вызываете adapter.notify*()

  • Recyclerview записывает изменение (в AdapterHelper.mPendingUpdates, если я правильно понимаю код). Это изменение будет отражено в ViewHolder.getAdapterPosition(), но еще не в ViewHolder.getLayoutPosition().

  • В какой-то момент recyclerView применяет записанные изменения, в основном он согласовывает точку макета с точки зрения адаптера. Кажется, что это может произойти до того, как макет пройдет.

1., 2., 3. последовательность может происходить любое количество раз, пока 2. сразу следует за 1. (и оба происходят в основном потоке).

(1. => 2. => 3.) ... (1. => 2. => 3.) ... 4. 

Ответ 4

Item firstItem = new Item(0, "zero");

list.add(firstItem);

adapter.notifyItemInserted(list.indexOf(firstItem));

//Other action...

Item nextItem = new Item(2, "two");

list.add(nextItem);

adapter.notifyItemInserted(list.indexOf(nextItem))