Как сохранить позицию прокрутки RecyclerView с помощью RecyclerView.State?

У меня есть вопрос об Android RecyclerView.State.

Я использую RecyclerView, как я могу использовать и привязывать его к RecyclerView.State?

Моя цель - сохранить положение прокрутки RecyclerView.

Ответ 1

Как вы планируете сохранить последнюю сохраненную позицию с RecyclerView.State?

Вы всегда можете положиться на хорошее состояние сохранения. Расширьте RecyclerView и переопределите onSaveInstanceState() и onRestoreInstanceState():

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
            mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
        }
        SavedState newState = new SavedState(superState);
        newState.mScrollPosition = mScrollPosition;
        return newState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        if(state != null && state instanceof SavedState){
            mScrollPosition = ((SavedState) state).mScrollPosition;
            LayoutManager layoutManager = getLayoutManager();
            if(layoutManager != null){
              int count = layoutManager.getItemCount();
              if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
                  layoutManager.scrollToPosition(mScrollPosition);
              }
            }
        }
    }

    static class SavedState extends android.view.View.BaseSavedState {
        public int mScrollPosition;
        SavedState(Parcel in) {
            super(in);
            mScrollPosition = in.readInt();
        }
        SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(mScrollPosition);
        }
        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

Ответ 2

Если вы используете LinearLayoutManager, он поставляется с предварительно созданным сохранением api linearLayoutManagerInstance.onSaveInstanceState() и восстанавливает api linearLayoutManagerInstance.onRestoreInstanceState(...)

При этом вы можете сохранить возвращенный аргумент в свой outState. например,

outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());

и восстановите позицию восстановления с сохраненным состоянием. например,

Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);

Чтобы завершить все, ваш окончательный код будет выглядеть примерно так:

private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";

/**
 * This is a method for Fragment. 
 * You can do the same in onCreate or onRestoreInstanceState
 */
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);

    if(savedInstanceState != null)
    {
        Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
        recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}

Изменить: вы также можете использовать один и тот же apis с GridLayoutManager, поскольку это подкласс LinearLayoutManager. Спасибо @wegsehen за предложение.

Изменить: помните, что если вы также загружаете данные в фоновый поток, вам нужно вызвать onRestoreInstanceState в вашем методе onPostExecute/onLoadFinished для позиции, которая будет восстановлена при изменении ориентации, например

@Override
protected void onPostExecute(ArrayList<Movie> movies) {
    mLoadingIndicator.setVisibility(View.INVISIBLE);
    if (movies != null) {
        showMoviePosterDataView();
        mDataAdapter.setMovies(movies);
      mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
        } else {
            showErrorMessage();
        }
    }

Ответ 3

хранить

lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

Восстановить

((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);

и если это не сработает, попробуйте

((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)

Поместите хранилище в onPause() и восстановите в onResume()

Ответ 4

I Задайте переменные в onCreate(), сохраните положение прокрутки в onPause() и установите положение прокрутки в onResume()

public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;

@Override
public void onCreate(Bundle savedInstanceState)
{
    //Set Variables
    super.onCreate(savedInstanceState);
    cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
    mLayoutManager = new LinearLayoutManager(this);
    cRecyclerView.setHasFixedSize(true);
    cRecyclerView.setLayoutManager(mLayoutManager);

}

@Override
public void onPause()
{
    super.onPause();
    //read current recyclerview position
    index = mLayoutManager.findFirstVisibleItemPosition();
    View v = cRecyclerView.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}

@Override
public void onResume()
{
    super.onResume();
    //set recyclerview position
    if(index != -1)
    {
        mLayoutManager.scrollToPositionWithOffset( index, top);
    }
}

Ответ 5

Я хотел сохранить позицию прокрутки Recycler View, когда вы переходите от моего списка, а затем нажмите кнопку "Назад", чтобы вернуться назад. Многие из решений, предоставленных для этой проблемы, были либо намного сложнее, чем необходимо, либо не работали для моей конфигурации, поэтому я решил поделиться своим решением.

Сначала сохраните состояние экземпляра в onPause, как показано многими. Я считаю, что здесь стоит подчеркнуть, что эта версия onSaveInstanceState является методом класса RecyclerView.LayoutManager.

private LinearLayoutManager mLayoutManager;
Parcelable state;

        @Override
        public void onPause() {
            super.onPause();
            state = mLayoutManager.onSaveInstanceState();
        }

Ключ к правильной работе этого устройства - убедиться, что вы вызываете onRestoreInstanceState после того, как вы присоедините свой адаптер, как некоторые указали в других потоках. Однако вызов фактического метода намного проще, чем многие указали.

private void someMethod() {
    mVenueRecyclerView.setAdapter(mVenueAdapter);
    mLayoutManager.onRestoreInstanceState(state);
}

Ответ 6

Вам больше не нужно спасать и восстанавливать состояние самостоятельно. Если вы установите уникальный идентификатор в xml и recyclerView.setSaveEnabled(true) (по умолчанию true), система сделает это автоматически. Вот больше об этом: http://trickyandroid.com/saving-android-view-state-correctly/

Ответ 7

Вот как я восстанавливаю позицию RecyclerView с помощью GridLayoutManager после вращения, когда вам необходимо перезагрузить данные из Интернета с помощью AsyncTaskLoader.

Создайте глобальную переменную Parcelable и GridLayoutManager и статическую конечную строку:

private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";

Сохранить состояние gridLayoutManager в onSaveInstance()

 @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, 
            mGridLayoutManager.onSaveInstanceState());
        }

Восстановить в onRestoreInstanceState

@Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        //restore recycler view at same position
        if (savedInstanceState != null) {
            savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
        }
    }

Затем, когда загрузчик извлекает данные из Интернета, вы восстанавливаете позицию recyclerview в onLoadFinished()

if(savedRecyclerLayoutState!=null){
                mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
            }

Конечно, вам нужно создать экземпляр gridLayoutManager внутри onCreate. ура

Ответ 8

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

android:configChanges="orientation|screenSize"

Затем в вашем фрагменте объявите этот метод экземпляра

private  Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;

Добавьте ниже код в свой метод @onPause

@Override
public void onPause() {
    super.onPause();
    //This used to store the state of recycler view
    mBundleRecyclerViewState = new Bundle();
    mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
    mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}

Добавьте метод onConfigartionChanged, как показано ниже.

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    //When orientation is changed then grid column count is also changed so get every time
    Log.e(TAG, "onConfigurationChanged: " );
    if (mBundleRecyclerViewState != null) {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
                mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);

            }
        }, 50);
    }
    mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}

Этот аспект решит вашу проблему. если у вас есть менеджер разметки, тогда просто замените диспетчер верхней компоновки.

Ответ 9

Все менеджеры компоновки, входящие в состав библиотеки поддержки, уже знают, как сохранить и восстановить позицию прокрутки.

Положение прокрутки восстанавливается при первом проходе макета, что происходит при соблюдении следующих условий:

  • менеджер раскладок прикреплен к RecyclerView
  • adpater прикрепляется к RecyclerView

Обычно вы устанавливаете менеджер макета в XML, поэтому все, что вам нужно сделать сейчас, это

  1. Загрузите адаптер с данными
  2. Затем подключите адаптер к RecyclerView

Вы можете сделать это в любое время, это не ограничено Fragment.onCreateView или Activity.onCreate.

Пример. Убедитесь, что адаптер подключен при каждом обновлении данных.

viewModel.liveData.observe(this) {
    // Load adapter.
    adapter.data = it

    if (list.adapter != adapter) {
        // Only set the adapter if we didn't do it already.
        list.adapter = adapter
    }
}

Сохраненная позиция прокрутки рассчитывается по следующим данным:

  • Положение адаптера первого элемента на экране
  • Пиксельное смещение верхней части элемента от верхней части списка (для вертикального расположения)

Поэтому вы можете ожидать небольшое несоответствие, например, если ваши предметы имеют разные размеры в портретной и альбомной ориентации.

Ответ 10

На Android API Level 28 я просто гарантирую, что я настроил свои LinearLayoutManager и RecyclerView.Adapter в моем методе Fragment#onCreateView, и все, что только что работало ™. Мне не нужно было выполнять какие-либо работы onSaveInstanceState или onRestoreInstanceState.

Ответ Евгения Печанца объясняет, почему это работает.

Ответ 11

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

Ответ 12

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

Мое требование к проекту: Итак, у меня есть RecyclerView, в котором перечислены некоторые элементы, доступные для кликов, в моей основной деятельности (Activity-A). Когда элемент кликается, отображается новое действие с информацией о деталях (Activity-B).

Я внедрил в файле манифеста, что Activity-A является родителем Activity-B, таким образом, у меня есть кнопка возврата или дома на ActionBar Activity-B

Проблема: каждый раз, когда я нажимал кнопку "Назад" или "Домой" в ActionBar Activity-B, Activity-A переходит в полный жизненный цикл активности, начиная с onCreate()

Несмотря на то, что я реализовал onSaveInstanceState(), сохраняющий список адаптера RecyclerView, когда Activity-A запускает его жизненный цикл, saveInstanceState всегда имеет значение null в методе onCreate().

Дальше копаясь в Интернете, я столкнулся с одной и той же проблемой, но человек заметил, что кнопка "Назад" или "Домой" ниже устройства Anroid (кнопка "Назад/Главная по умолчанию"), Activity-A не переходит в жизненный цикл активности.

Решение:

  1. Я удалил тег для родительской активности в манифесте для Activity-B
  2. Я включил домашнюю или заднюю кнопку Activity-B

    В методе onCreate() добавьте эту строку supportActionBar?.SetDisplayHomeAsUpEnabled(true)

  3. В методе overide fun onOptionsItemSelected() я проверил элемент item.ItemId, на который нажата кнопка на основе идентификатора. Идентификатор для кнопки "Назад"

    android.R.id.home

    Затем выполните вызов функции finish() внутри файла android.R.id.home

    Это закончит Activity-B и принесет Acitivy-A, не пройдя весь жизненный цикл.

Для моего требования это лучшее решение.

     override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_show_project)

    supportActionBar?.title = projectName
    supportActionBar?.setDisplayHomeAsUpEnabled(true)

}


 override fun onOptionsItemSelected(item: MenuItem?): Boolean {

    when(item?.itemId){

        android.R.id.home -> {
            finish()
        }
    }

    return super.onOptionsItemSelected(item)
}

Ответ 13

У меня было одно и то же требование, но решения здесь не совсем поняли меня по линии, из-за источника данных для recyclerView.

Я извлекал состояние LinearLayoutManager в RecyclerViews в onPause()

private Parcelable state;

Public void onPause() {
    state = mLinearLayoutManager.onSaveInstanceState();
}

Parcelable state сохраняется в onSaveInstanceState(Bundle outState) и снова извлекается в onCreate(Bundle savedInstanceState), когда savedInstanceState != null.

Однако адаптер recyclerView заполняется и обновляется объектом ViewModel LiveData, возвращаемым вызовом DAO в базу данных Room.

mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
    @Override
    public void onChanged(@Nullable final List<String> displayStrings) {
        mAdapter.swapData(displayStrings);
        mLinearLayoutManager.onRestoreInstanceState(state);
    }
});

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

Я подозреваю, что это происходит потому, что состояние LinearLayoutManager которое я восстановил, было перезаписано, когда данные были возвращены вызовом базы данных, или восстановленное состояние было бессмысленным в отношении "пустого" LinearLayoutManager.

Если данные адаптера доступны напрямую (т.е. Не прибегают к вызову базы данных), то восстановление состояния экземпляра в LinearLayoutManager может быть выполнено после того, как адаптер установлен в recyclerView.

Различие между двумя сценариями удерживало меня на века.

Ответ 14

Activity.java:

public RecyclerView.LayoutManager mLayoutManager;

Parcelable state;

    mLayoutManager = new LinearLayoutManager(this);


// Inside `onCreate()` lifecycle method, put the below code :

    if(state != null) {
    mLayoutManager.onRestoreInstanceState(state);

    } 

    @Override
    protected void onResume() {
        super.onResume();

        if (state != null) {
            mLayoutManager.onRestoreInstanceState(state);
        }
    }   

   @Override
    protected void onPause() {
        super.onPause();

        state = mLayoutManager.onSaveInstanceState();

    }   

Почему я использую OnSaveInstanceState() в onPause() означает, что при вызове другого действия onPause будет вызываться. Он сохранит эту позицию прокрутки и восстановит позицию, когда мы вернемся из другого действия.