it-roy-ru.com

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

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

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

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

85
陈文涛

Как вы планируете сохранить последнюю сохраненную позицию с помощью 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.getChildCount();
              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];
            }
        };
    }
32
Nikola Despotoski

Если вы используете 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());
}

Edit: Вы также можете использовать тот же API-интерфейс с GridLayoutManager, так как это подкласс LinearLayoutManager. Спасибо @wegsehen за предложение.

Edit: Помните, что если вы также загружаете данные в фоновом потоке, вам необходимо вызвать метод 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();
        }
    }
181
Gökhan Barış Aker

Хранить

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

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

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

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

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

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

66
Gillis Haasnoot

Я устанавливаю переменные в 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);
    }
}
16
farhad.kargaran

Я хотел сохранить положение прокрутки в 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);
}
13
Braden Holt

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

6
AppiDevo

Вот как я восстанавливаю позицию 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);
        }
    }

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

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

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

3
Farmaker

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

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

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

2
Pawan kumar sharma

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

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

  • менеджер компоновки прикреплен к 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
    }
}

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

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

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

1
Eugen Pechanec

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

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

0
Heath Borders

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

0
Ronaldo Albertini

У меня было такое же требование, но решения здесь не совсем помогли мне, из-за источника данных для 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.

Различие между этими двумя сценариями держало меня целую вечность.

0
MikeP

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

Требования к моему проекту: Итак, у меня есть RecyclerView, в котором перечислены некоторые интерактивные элементы в моей основной активности (Activity-A). При щелчке по элементу отображается новое действие с деталями элемента (действие-B).

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

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

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

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

Решение:

  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.

    Это завершит деятельность-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)
}
0
Andy Parinas