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

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

Как обновить данные, отображаемые в RecyclerView (вызов notifyDataSetChanged на его адаптере), и убедиться, что позиция прокрутки reset точно там, где она была?

В случае хорошего ol 'ListView все, что требуется, - это получение getChildAt(0), проверка его getTop() и вызов setSelectionFromTop с теми же точными данными впоследствии.

Это не представляется возможным в случае RecyclerView.

Я предполагаю, что я должен использовать его LayoutManager, который действительно предоставляет scrollToPositionWithOffset(int position, int offset), но какой правильный способ получить позицию и смещение?

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

Или есть более элегантный способ получить работу?

4b9b3361

Ответ 1

Я использую этот. ^ _ ^

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

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

Это проще, надеюсь, что это поможет вам!

Ответ 2

У меня очень похожая проблема. И я придумал следующее решение.

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

Например, если вам нужно только обновить или, другими словами, вы хотите, чтобы каждое представление было перестроено, просто выполните следующее:

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

Ответ 3

РЕДАКТИРОВАТЬ: Чтобы восстановить ту же самую видимую позицию, что и в случае, сделайте ее похожей на нее, нам нужно сделать что-то немного другое (см. ниже, как восстановить точное значение 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, который, скорее всего, сделает список другим. ---

  • Примените 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 имеет reset все свои данные reset, прокрутите с помощью этого:

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

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

Ответ 4

Да, вы можете решить эту проблему, сделав конструктор адаптера только один раз, я объясняю здесь часть кода:

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

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

Теперь вы можете видеть, что я проверил, что адаптер нулевой или нет, и инициализировать только тогда, когда он нулевой.

Если адаптер не равен нулю, я уверен, что инициализировал мой адаптер хотя бы один раз.

Поэтому я просто добавлю список к адаптеру и вызову notifydatasetchanged.

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

Спасибо Счастливого Кодирования

Ответ 5

Я не использовал Recyclerview, но я сделал это в ListView. Пример кода в Recyclerview:

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

Это прослушиватель, когда пользователь прокручивает. Накладные расходы на производительность незначительны. И первая видимая позиция точно такая.

Ответ 6

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

Решение здесь заключается в том, чтобы продолжать прокручивать обзор повторов при появлении нового сообщения.

Метод onChanged() обнаруживает действие, выполняемое в режиме просмотра.

Ответ 7

Сохраняйте позицию прокрутки, используя ответ @DawnYu, чтобы обернуть notifyDataSetChanged() следующим образом:

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

Ответ 8

Это работает для меня в Котлине.

  1. Создайте адаптер и передайте ваши данные в конструктор
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. измените это свойство и вызовите notifyDataSetChanged()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

Смещение прокрутки не изменяется.

Ответ 9

У меня была эта проблема со списком элементов, каждый из которых имел время в минутах, пока они не были "должными" и не нуждались в обновлении. Я бы обновил данные, а затем, позвонил

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

Ответ 10

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

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

У меня была эта проблема, когда я запустил другое действие, запущенное из элемента повторного просмотра, когда я вернулся и установил обновление одного поля в одном элементе с помощью notifyItemChanged (position) прокрутки перемещений RV, и я пришел к выводу, что автофокусировка EditText Элементы, код выше решил мою проблему.

Лучший.

Ответ 11

Просто верните, если 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);
}

Ответ 12

Возможно, это слишком просто, но я делаю именно это, просто позвонив

recreate();

Сохраняется позиция моего ресайклера.