У мене є питання щодо Android RecyclerView.State .
Я використовую RecyclerView, як я можу використовувати його і пов'язувати його з RecyclerView.State?
Моя мета - зберегти позицію прокрутки RecyclerView .
У мене є питання щодо Android RecyclerView.State .
Я використовую RecyclerView, як я можу використовувати його і пов'язувати його з RecyclerView.State?
Моя мета - зберегти позицію прокрутки RecyclerView .
Відповіді:
Як ви плануєте зберегти останню збережену позицію RecyclerView.State
?
Ви завжди можете розраховувати на добрий стан економії. Розширення RecyclerView
та перезапис наSaveInstanceState () та 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];
}
};
}
Починаючи з recyclerview:1.2.0-alpha02
випуску StateRestorationPolicy
було введено. Це може бути кращим підходом до даної проблеми.
Ця тема висвітлюється в середній статті розробників для Android .
Крім того, @ rubén-viguera поділився більше деталями у відповіді нижче. https://stackoverflow.com/a/61609823/892500
Якщо ви використовуєте 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();
}
}
RecyclerView
але не тоді , коли у вас є ViewPager
з RecycerView
на кожній сторінці.
onCreateView
, а після завантаження даних. Дивіться відповідь @Farmaker тут.
Магазин
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Відновлення
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
і якщо це не працює, спробуйте
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Покладіть магазин onPause()
і відновітьonResume()
lastFirstVisiblePosition
до 0
після відновлення його. Цей випадок корисний, коли у вас є фрагмент / активність, що завантажує різні дані в одне RecyclerView
, наприклад, програма провідника файлів.
SwipeRefreshLayout
?
RecyclerView
Я хотів зберегти позицію прокрутки Recycler View, коли відходив від діяльності зі списку, а потім натиснув кнопку "Назад", щоб повернутися назад. Багато варіантів рішень для цієї проблеми були або набагато складнішими, ніж потрібно, або не працювали для моєї конфігурації, тому я подумав, що поділюся своїм рішенням.
Спочатку збережіть стан свого примірника в onPause, як показали багато. Я думаю, тут варто підкреслити, що ця версія onSaveInstanceState є методом з класу RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Ключовим фактором для правильної роботи є переконайтеся, що ви зателефонуєте наReRestoreInstanceState після того, як ви приєднаєте адаптер, як це було зазначено в інших потоках. Однак фактичний виклик методу набагато простіший, ніж багато хто вказав.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
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);
}
}
Вам більше не потрібно врятувати та відновлювати стан. Якщо ви встановите унікальний ідентифікатор у xml та recilerView.setSaveEnabled (true) (true за замовчуванням) система автоматично зробить це. Більше про це: http://trickyandroid.com/saving-android-view-state-cor правильно/
Усі менеджери макетів, що входять у бібліотеку підтримки, вже знають, як зберегти та відновити позицію прокрутки.
Позиція прокрутки відновлюється під час першого пропуску макета, що відбувається, коли виконуються наступні умови:
RecyclerView
RecyclerView
Зазвичай ви встановлюєте диспетчер макетів у XML, тому все, що вам потрібно зробити зараз
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
}
}
Збережена позиція прокрутки обчислюється з таких даних:
Тому ви можете очікувати незначної невідповідності, наприклад, якщо ваші предмети мають різні розміри в портретній та пейзажній орієнтації.
RecyclerView
/ ScrollView
/ Що потрібно мати android:id
для своєї держави , щоб врятуватися.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Ось мій підхід до збереження їх та підтримання стану рециркулятора в фрагменті. Спочатку додайте зміни конфігурації у батьківській діяльності, як показано нижче.
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);
}
Цей підхід вирішить вашу проблему. якщо у вас різний диспетчер макетів, просто замініть верхній диспетчер верстки.
Так я відновляю позицію 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);
}
}
Потім, коли завантажувач отримує дані з Інтернету, ви відновлюєте позицію reciklierview в режимі onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. звичайно вам доведеться інстанціювати gridLayoutManager всередині onCreate. Ура
У мене була така ж вимога, але рішення тут не дуже перешкодили мені через джерело даних для recilerView.
Я витягував стан LinearLayoutManager RecyclerViews в режимі onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Парцелябел state
зберігається в onSaveInstanceState(Bundle outState)
, і знову вилучається в onCreate(Bundle savedInstanceState)
, колиsavedInstanceState != null
.
Однак адаптер recilerView заповнюється та оновлюється об’єктом ViewModel LiveData, який повертається викликом DAO до бази даних кімнати.
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 може бути виконано після встановлення адаптера на recilerView.
Різниця між двома сценаріями тримала мене на віки.
На Android API Level 28 я просто гарантую, що я налаштував свій LinearLayoutManager
і RecyclerView.Adapter
в своєму Fragment#onCreateView
методі, і все, що тільки працював ™ ️. Мені не потрібно було нічого робити onSaveInstanceState
чи onRestoreInstanceState
працювати.
Відповідь Євгена Печанека пояснює, чому це працює.
Починаючи з версії 1.2.0-альфа02 бібліотеки для androidx recilerView, тепер ним керується автоматично. Просто додайте його:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
І використовуйте:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
Перелік StateRestorationPolicy має 3 варіанти:
Зауважте, що під час цієї відповіді бібліотека recilerView все ще знаходиться в альфа03, але альфа-фаза не підходить для виробничих цілей .
Для мене проблема полягала в тому, що я встановлював новий менеджер верстки щоразу, коли міняв адаптер, втрачаючи позицію прокрутки черга на recilerView.
Я просто хотів би поділитися недавньою проблемою, з якою я стикаюся з RecyclerView. Я сподіваюся, що кожен, хто відчуває ту саму проблему, піде на користь.
Моя вимога проекту: Отже, у мене є RecyclerView, який перераховує деякі елементи, які можна натиснути в моїй Основній діяльності (Activity-A). При натисканні на пункт з'являється нова активність із деталями елемента (активність-B).
Я реалізовувався в Маніфесті файлі той факт, що Activity-A є батьківською мовою Activity-B, таким чином, у мене є кнопка "назад" чи "домашня сторінка" на панелі ActionBar активності-B
Проблема: Кожного разу, коли я натискав кнопку "Назад" або "Головна" в панелі "Діяльність-Б", активність-А переходить у повний життєвий цикл діяльності, починаючи з onCreate ()
Навіть незважаючи на те, що я реалізував OnSaveInstanceState (), що зберігає список адаптера RecyclerView, коли Activity-A починає його життєвий цикл, метод saveInstanceState завжди є нульовим у методі onCreate ().
Подальше копаючись в Інтернеті, я зіткнувся з тією ж проблемою, але людина помітила, що кнопка "Назад" або "Головна" під пристроєм Anroid (кнопка "Назад / Головна") Activity-A не переходить у життєвий цикл діяльності.
Рішення:
Я ввімкнув кнопку "Додому" або "Назад" на "Активність-Б"
Під методом onCreate () додайте цей рядок supportActionBar? .SetDisplayHomeAsUpEnabled (true)
У методі overide fun onOptionsItemSelected () я перевірив на предмет item.ItemId, на який елемент натискається на основі ідентифікатора. Ідентифікатор кнопки "назад" є
android.R.id.home
Потім реалізуйте виклик функції закінчення () всередині будинку android.R.id.home
Це припинить Діяльність-Б і приведе 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)
}
У мене була така ж проблема з незначною різницею, я використовував Databinding , і я встановлював переробник recilerView та layoutManager в методах зв'язування.
Проблема полягала в тому, що прив'язка даних відбувалася після onResume, тому вона скидала мій макет-менеджер після onResume, і це означає, що він прокручується назад до початкового місця кожного разу. Тож коли я зупинив налаштування layoutManager в методах адаптера даних, це знову працює добре.
У моєму випадку я встановлював RecyclerView layoutManager
і в XML, і в onViewCreated
. Видалення завдання onViewCreated
зафіксовано.
with(_binding.list) {
// layoutManager = LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}
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 буде called.It збереже цю позицію прокрутки і відновити становище , коли повертаючись з іншої діяльності.