Saya punya pertanyaan tentang RecyclerView.State Android .
Saya menggunakan RecyclerView, bagaimana saya bisa menggunakan dan mengikatnya dengan RecyclerView.State?
Tujuan saya adalah untuk menyimpan posisi gulir RecyclerView .
Saya punya pertanyaan tentang RecyclerView.State Android .
Saya menggunakan RecyclerView, bagaimana saya bisa menggunakan dan mengikatnya dengan RecyclerView.State?
Tujuan saya adalah untuk menyimpan posisi gulir RecyclerView .
Jawaban:
Bagaimana rencana Anda untuk menyimpan posisi terakhir yang disimpan RecyclerView.State
?
Anda selalu bisa mengandalkan keadaan penyelamatan yang baik. Perluas RecyclerView
dan timpa onSaveInstanceState () dan 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];
}
};
}
Mulai dari recyclerview:1.2.0-alpha02
rilis StateRestorationPolicy
telah diperkenalkan. Ini bisa menjadi pendekatan yang lebih baik untuk masalah yang diberikan.
Topik ini telah dibahas pada artikel medium pengembang android .
Juga, @ rubén-viguera membagikan lebih banyak detail dalam jawaban di bawah ini. https://stackoverflow.com/a/61609823/892500
Jika Anda menggunakan LinearLayoutManager, LinearLayoutManager dilengkapi dengan save api linearLayoutManagerInstance.onSaveInstanceState () dan pulihkan api linearLayoutManagerInstance.onRestoreInstanceState (...)
Dengan itu, Anda dapat menyimpan parsel yang dikembalikan ke negara bagian Anda. misalnya,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
, dan pulihkan posisi pemulihan dengan keadaan yang Anda simpan. misalnya,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Untuk menyelesaikan semuanya, kode akhir Anda akan terlihat seperti ini
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: Anda juga dapat menggunakan apis yang sama dengan GridLayoutManager, karena ini adalah subclass dari LinearLayoutManager. Terima kasih @wegsehen atas sarannya.
Edit: Ingat, jika Anda juga memuat data di thread latar belakang, Anda perlu memanggil onRestoreInstanceState dalam metode onPostExecute / onLoadFinished Anda agar posisi dipulihkan setelah orientasi berubah, mis.
@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
tetapi tidak jika Anda memiliki ViewPager
dengan a RecycerView
di setiap halaman.
onCreateView
, tetapi setelah memuat data. Lihat jawaban @Farmaker di sini.
Toko
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Mengembalikan
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
dan jika itu tidak berhasil, coba
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Simpan onPause()
dan pulihkanonResume()
lastFirstVisiblePosition
ke 0
setelah memulihkannya. Kasus ini berguna jika Anda memiliki fragmen / aktivitas yang memuat data berbeda dalam satu RecyclerView
, misalnya, aplikasi file explorer.
SwipeRefreshLayout
?
RecyclerView
Saya ingin menyimpan posisi gulir Recycler View saat menavigasi keluar dari aktivitas daftar saya, lalu mengklik tombol kembali untuk menavigasi kembali. Banyak solusi yang disediakan untuk masalah ini jauh lebih rumit daripada yang dibutuhkan atau tidak berfungsi untuk konfigurasi saya, jadi saya pikir saya akan membagikan solusi saya.
Pertama-tama simpan status instance Anda di onPause seperti yang telah ditunjukkan banyak orang. Saya pikir perlu ditekankan di sini bahwa versi onSaveInstanceState ini adalah metode dari kelas RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Kunci agar ini berfungsi dengan benar adalah memastikan Anda memanggil onRestoreInstanceState setelah Anda memasang adaptor, seperti yang ditunjukkan beberapa di utas lain. Namun panggilan metode sebenarnya jauh lebih sederhana daripada yang telah ditunjukkan banyak orang.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
Saya mengatur variabel di onCreate (), menyimpan posisi gulir di onPause () dan mengatur posisi gulir di 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);
}
}
Anda tidak perlu lagi menyimpan dan memulihkan keadaan sendiri. Jika Anda menyetel ID unik di xml dan recyclerView.setSaveEnabled (true) (true secara default), sistem akan otomatis melakukannya. Berikut lebih lanjut tentang ini: http://trickyandroid.com/saving-android-view-state-correctly/
Semua manajer tata letak yang dibundel di pustaka dukungan sudah tahu cara menyimpan dan memulihkan posisi gulir.
Posisi gulir dipulihkan pada tata letak pertama yang terjadi ketika kondisi berikut terpenuhi:
RecyclerView
RecyclerView
Biasanya Anda menyetel pengelola tata letak dalam XML sehingga yang harus Anda lakukan sekarang adalah
RecyclerView
Anda dapat melakukan ini kapan saja, tidak dibatasi Fragment.onCreateView
atau Activity.onCreate
.
Contoh: Pastikan adaptor terpasang setiap kali data diperbarui.
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
}
}
Posisi gulir yang disimpan dihitung dari data berikut:
Oleh karena itu, Anda dapat mengharapkan sedikit ketidakcocokan, misalnya jika item Anda memiliki dimensi berbeda dalam orientasi potret dan lanskap.
The RecyclerView
/ ScrollView
/ apapun perlu memiliki android:id
bagi negara untuk diselamatkan.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Berikut adalah Pendekatan saya untuk menyimpannya dan mempertahankan status recyclerview dalam sebuah fragmen. Pertama, tambahkan perubahan konfigurasi pada aktivitas induk Anda seperti kode di bawah ini.
android:configChanges="orientation|screenSize"
Kemudian di Fragmen Anda, deklarasikan metode instance ini
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Tambahkan kode di bawah ini dalam metode @onPause Anda
@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);
}
Tambahkan Metode onConfigartionChanged seperti di bawah ini.
@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);
}
Pendekatan ini akan menyelesaikan masalah Anda. jika Anda memiliki pengelola tata letak yang berbeda, maka ganti saja pengelola tata letak atas.
Ini adalah cara saya mengembalikan posisi RecyclerView dengan GridLayoutManager setelah rotasi ketika Anda perlu memuat ulang data dari internet dengan AsyncTaskLoader.
Buat variabel global Parcelable dan GridLayoutManager dan string final statis:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Simpan status gridLayoutManager di onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Pulihkan di 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);
}
}
Kemudian saat loader mengambil data dari internet, Anda mengembalikan posisi recyclerview di onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
..tentu saja Anda harus membuat instance gridLayoutManager di dalam onCreate. Bersulang
Saya memiliki persyaratan yang sama, tetapi solusi di sini tidak cukup membantu saya, karena sumber data untuk recyclerView.
Saya mengekstrak status LinearLayoutManager RecyclerViews di onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
disimpan di onSaveInstanceState(Bundle outState)
, dan diekstraksi lagi di onCreate(Bundle savedInstanceState)
, saat savedInstanceState != null
.
Namun, adaptor recyclerView diisi dan diperbarui oleh objek ViewModel LiveData yang ditampilkan oleh panggilan DAO ke database 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);
}
});
Saya akhirnya menemukan bahwa memulihkan status instance langsung setelah data disetel di adaptor akan menjaga status gulir di seluruh rotasi.
Saya menduga ini baik karena LinearLayoutManager
status yang saya pulihkan telah ditimpa ketika data dikembalikan oleh panggilan database, atau status yang dipulihkan tidak berarti terhadap LinearLayoutManager 'kosong'.
Jika data adaptor tersedia secara langsung (misalnya, tidak terkait pada panggilan database), maka memulihkan status instance di LinearLayoutManager bisa dilakukan setelah adaptor disetel pada recyclerView.
Perbedaan antara kedua skenario tersebut membuat saya bertahan lama.
Di Android API Level 28, saya cukup memastikan bahwa saya menyiapkan LinearLayoutManager
dan RecyclerView.Adapter
dalam Fragment#onCreateView
metode saya , dan semuanya Just Worked ™ ️. Saya tidak perlu melakukan apa pun onSaveInstanceState
atau onRestoreInstanceState
bekerja.
Jawaban Eugen Pechanec menjelaskan mengapa ini berhasil.
Mulai dari versi 1.2.0-alpha02 library androidx recyclerView, sekarang dikelola secara otomatis. Tambahkan saja dengan:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
Dan gunakan:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
Enum StateRestorationPolicy memiliki 3 opsi:
Perhatikan bahwa pada saat jawaban ini, pustaka recyclerView masih dalam alpha03, tetapi fase alfa tidak cocok untuk tujuan produksi .
Bagi saya, masalahnya adalah saya menyiapkan layoutmanager baru setiap kali saya mengganti adaptor saya, kehilangan posisi gulir antrean di recyclerView.
Saya hanya ingin berbagi masalah yang baru-baru ini saya hadapi dengan RecyclerView. Saya berharap siapa pun yang mengalami masalah yang sama akan mendapat manfaat.
Persyaratan Proyek Saya: Jadi saya memiliki RecyclerView yang mencantumkan beberapa item yang dapat diklik di Aktivitas Utama saya (Aktivitas-A). Saat Item diklik, Aktivitas baru ditampilkan dengan Detail Item (Aktivitas-B).
Saya mengimplementasikan dalam file Manifest bahwa Activity-A adalah induk dari Activity-B, dengan begitu, saya memiliki tombol kembali atau home pada ActionBar dari Activity-B
Masalah: Setiap kali saya menekan tombol Back atau Home di Activity-B ActionBar, Activity-A masuk ke Activity Life Cycle penuh mulai dari onCreate ()
Meskipun saya mengimplementasikan onSaveInstanceState () yang menyimpan Daftar Adaptor RecyclerView, ketika Activity-A memulai siklus hidupnya, saveInstanceState selalu nol dalam metode onCreate ().
Menggali lebih lanjut di internet, saya menemukan masalah yang sama tetapi orang tersebut memperhatikan bahwa tombol Kembali atau Beranda di bawah perangkat Anroid (tombol Kembali / Beranda Default), Aktivitas-A tidak masuk ke Siklus Hidup Aktivitas.
Larutan:
Saya mengaktifkan tombol beranda atau kembali pada Aktivitas-B
Di bawah metode onCreate () tambahkan baris ini supportActionBar? .SetDisplayHomeAsUpEnabled (true)
Dalam metode onOptionsItemSelected () overide fun, saya memeriksa item.ItemId di mana item diklik berdasarkan id. Id untuk tombol kembali adalah
android.R.id.home
Kemudian implementasikan panggilan fungsi finish () di dalam android.R.id.home
Ini akan mengakhiri Aktivitas-B dan membawa Aktivitas-A tanpa melalui seluruh siklus hidup.
Untuk kebutuhan saya, sejauh ini ini adalah solusi terbaik.
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)
}
Saya memiliki masalah yang sama dengan perbedaan kecil, saya menggunakan Databinding , dan saya menyetel adaptor recyclerView dan layoutManager dalam metode binding.
Masalahnya adalah pengikatan data terjadi setelah onResume jadi itu menyetel ulang layoutManager saya setelah onResume dan itu berarti itu menggulir kembali ke tempat semula setiap waktu. Jadi, ketika saya berhenti menyetel layoutManager dalam metode adaptor Databinding, itu berfungsi dengan baik lagi.
Dalam kasus saya, saya menyetel RecyclerView layoutManager
dalam XML dan dalam onViewCreated
. Menghapus tugas di onViewCreated
memperbaikinya.
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();
}
Why I'm using OnSaveInstanceState()
in onPause()
means, While beralih ke aktivitas lain onPause akan dipanggil. Ini akan menyimpan posisi gulir itu dan memulihkan posisi saat kita kembali dari aktivitas lain.