Android'in RecyclerView.State ile ilgili bir sorum var .
Bir RecyclerView kullanıyorum, onu RecyclerView.State ile nasıl kullanabilirim ve bağlayabilirim?
Amacım RecyclerView'ın kaydırma konumunu kaydetmek .
Android'in RecyclerView.State ile ilgili bir sorum var .
Bir RecyclerView kullanıyorum, onu RecyclerView.State ile nasıl kullanabilirim ve bağlayabilirim?
Amacım RecyclerView'ın kaydırma konumunu kaydetmek .
Yanıtlar:
İle son kaydedilen pozisyonu nasıl kaydetmeyi planlıyorsunuz RecyclerView.State
?
Her zaman iyi bir kaydetme durumuna güvenebilirsiniz. Uzatma RecyclerView
ve geçersiz kılma onSaveInstanceState () ve 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];
}
};
}
İtibaren recyclerview:1.2.0-alpha02
serbestStateRestorationPolicy
tanıtılmıştır. Verilen soruna daha iyi bir yaklaşım olabilir.
Bu konu, Android geliştiricileri orta makalesinde ele alınmıştır .
Ayrıca @ rubén-viguera, aşağıdaki yanıtta daha fazla ayrıntı paylaştı. https://stackoverflow.com/a/61609823/892500
LinearLayoutManager kullanıyorsanız, önceden oluşturulmuş kaydetme api linearLayoutManagerInstance.onSaveInstanceState () ve geri yükleme api ile birlikte gelir linearLayoutManagerInstance.onRestoreInstanceState (...)
Bununla, iade edilen parselleri kendi eyaletinize kaydedebilirsiniz. Örneğin,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
ve kaydettiğiniz durumla geri yükleme konumunu geri yükleyin. Örneğin,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Hepsini özetlemek gerekirse, son kodunuz şuna benzeyecek:
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());
}
Düzenleme: LinearLayoutManager'ın bir alt sınıfı olduğundan, aynı apileri GridLayoutManager ile de kullanabilirsiniz. Öneri için teşekkürler @wegsehen.
Düzenleme: Bir arka plan iş parçacığına da veri yüklüyorsanız, oryantasyon değişikliğinden sonra konumun geri yüklenmesi için onPostExecute / onLoadFinished yönteminiz içinde onRestoreInstanceState'e bir çağrı yapmanız gerekeceğini unutmayın, örn.
@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
ancak her sayfada bir ViewPager
ile varsa değil RecycerView
.
onCreateView
, veri yüklendikten sonra. @ Farmaker cevabına buradan bakın.
Mağaza
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Onarmak
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
ve bu işe yaramazsa, dene
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Mağazaya koy onPause()
ve geri yükleonResume()
lastFirstVisiblePosition
için 0
restore edildikten sonra. Bu durum, RecyclerView
örneğin bir dosya gezgini uygulamasında farklı verileri yükleyen bir parçanız / etkinliğiniz olduğunda kullanışlıdır .
SwipeRefreshLayout
?
RecyclerView
Liste etkinliğimden uzaklaşırken Recycler View'un kaydırma konumunu kaydetmek ve ardından geri gitmek için geri düğmesini tıklatmak istedim. Bu sorun için sağlanan çözümlerin çoğu ya gerekenden çok daha karmaşıktı ya da yapılandırmam için işe yaramadı, bu yüzden çözümümü paylaşacağımı düşündüm.
Öncelikle örnek durumunuzu birçoklarının gösterdiği gibi onPause'a kaydedin. Sanırım burada onSaveInstanceState'in bu sürümünün RecyclerView.LayoutManager sınıfından bir yöntem olduğunu vurgulamaya değer.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Bunun düzgün çalışmasını sağlamanın anahtarı, bazılarının diğer iş parçacıklarında da belirttiği gibi, bağdaştırıcınızı taktıktan sonra onRestoreInstanceState'i çağırdığınızdan emin olmaktır. Ancak gerçek yöntem çağrısı, birçoklarının belirttiğinden çok daha basittir.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
I değişkenleri onCreate () içinde ayarlayın, kaydırma konumunu onPause () içinde kaydedin ve onResume () içinde kaydırma konumunu ayarlayın
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);
}
}
Artık durumu kendi başınıza kaydetmek ve geri yüklemek zorunda değilsiniz. Xml ve recyclerView.setSaveEnabled (true) (varsayılan olarak true) içinde benzersiz bir kimlik belirlerseniz, sistem bunu otomatik olarak yapacaktır. İşte bununla ilgili daha fazla bilgi: http://trickyandroid.com/saving-android-view-state-correctly/
Destek kitaplığında bulunan tüm düzen yöneticileri, kaydırma konumunu nasıl kaydedip geri yükleyeceğini zaten biliyor.
Kaydırma konumu, aşağıdaki koşullar karşılandığında gerçekleşen ilk düzen geçişinde geri yüklenir:
RecyclerView
RecyclerView
Tipik olarak, düzen yöneticisini XML'de ayarlarsınız, böylece artık tek yapmanız gereken
RecyclerView
Bunu istediğiniz zaman yapabilirsiniz, bunlarla sınırlı değildir Fragment.onCreateView
veyaActivity.onCreate
.
Örnek: Veriler her güncellendiğinde adaptörün takılı olduğundan emin olun.
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
}
}
Kaydedilen kaydırma konumu aşağıdaki verilerden hesaplanır:
Bu nedenle, örneğin öğelerinizin dikey ve yatay yönde farklı boyutları varsa, hafif bir uyumsuzluk bekleyebilirsiniz.
RecyclerView
/ ScrollView
/ Bir olması gerekir ne olursa olsun android:id
onun devlet için kaydedilecek.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
İşte onları kurtarmak ve geri dönüştürücünün durumunu bir parça halinde sürdürmek için Benim Yaklaşımım. İlk olarak, aşağıdaki kod gibi ana etkinliğinize yapılandırma değişiklikleri ekleyin.
android:configChanges="orientation|screenSize"
Daha sonra Parçanızda bu örnek yöntemlerin
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
@OnPause yönteminize aşağıdaki kodu ekleyin
@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 Yöntemi aşağıdaki gibi ekleyin.
@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);
}
Bu yaklaşım, sorununuzu çözecektir. Farklı bir düzen yöneticiniz varsa, sadece üst düzen yöneticisini değiştirin.
AsyncTaskLoader ile internetten verileri yeniden yüklemeniz gerektiğinde GridLayoutManager ile RecyclerView konumunu bu şekilde geri yüklerim.
Parcelable ve GridLayoutManager'ın global bir değişkenini ve statik bir final dizesi oluşturun:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
GridLayoutManager durumunu onSaveInstance () içine kaydet
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
OnRestoreInstanceState içinde geri yükle
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//restore recycler view at same position
if (savedInstanceState != null) {
savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
}
Ardından, yükleyici internetten veri aldığında, onLoadFinished () içinde geri dönüşüm görünümü konumunu geri yüklersiniz.
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. elbette onCreate içinde gridLayoutManager'ı somutlaştırmanız gerekir. Şerefe
Ben de aynı gereksinime sahiptim, ancak buradaki çözümler recyclerView için veri kaynağı nedeniyle beni tam olarak aşamadı.
RecyclerViews 'LinearLayoutManager durumunu onPause () konumunda çıkarıyordum
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
, içine kaydedilir onSaveInstanceState(Bundle outState)
ve yeniden çıkarılır onCreate(Bundle savedInstanceState)
.savedInstanceState != null
.
Ancak, recyclerView bağdaştırıcısı bir Oda veritabanına DAO çağrısı tarafından döndürülen ViewModel LiveData nesnesi tarafından doldurulur ve güncellenir.
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
Sonunda, veri bağdaştırıcıda ayarlandıktan hemen sonra örnek durumunu geri yüklemenin kaydırma durumunu döndürmeler arasında koruyacağını buldum.
Bunun da nedeni LinearLayoutManager
veri veritabanı çağrısı tarafından döndürüldüğünde geri yüklediğim durumun üzerine yazıldığından ya da geri yüklenen durumun 'boş' bir LinearLayoutManager'a karşı anlamsız olduğundan şüpheleniyorum.
Adaptör verileri doğrudan mevcutsa (yani bir veritabanı çağrısında bitişik değilse), LinearLayoutManager'da örnek durumunun geri yüklenmesi adaptör recyclerView'da ayarlandıktan sonra yapılabilir.
İki senaryo arasındaki ayrım beni yıllarca ayakta tuttu.
Android API Seviye 28, ben sadece ben Setimden sağlamak LinearLayoutManager
ve RecyclerView.Adapter
benim de Fragment#onCreateView
️ ™ yönteminin ve her şey sadece çalıştı. Hiç yapmam onSaveInstanceState
ya da onRestoreInstanceState
çalışmam gerekmedi .
Eugen Pechanec'in cevabı bunun neden işe yaradığını açıklıyor.
Androidx recyclerView kitaplığının 1.2.0-alpha02 sürümünden başlayarak, artık otomatik olarak yönetiliyor. Şununla ekleyin:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
Ve kullan:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
StateRestorationPolicy numaralandırmasının 3 seçeneği vardır:
Bu cevabın verildiği sırada recyclerView kitaplığının hala alpha03'te olduğunu, ancak alfa aşamasının üretim amaçları için uygun olmadığını unutmayın .
Benim için sorun, adaptörümü her değiştirdiğimde recyclerView'da que scroll konumunu kaybederek yeni bir layoutmanager kurmamdı.
RecyclerView ile son karşılaştığım çıkmazı paylaşmak istiyorum. Umarım aynı sorunu yaşayan herkes fayda sağlar.
Proje Gereksinimim: Bu nedenle, Ana Aktivitemdeki (Aktivite-A) bazı tıklanabilir öğeleri listeleyen bir RecyclerView var. Kalem tıklandığında, Kalem Detayları (Aktivite-B) ile birlikte yeni bir Aktivite gösterilir.
Manifest'te uyguladım dosyasına Activity-A'nın Activity-B'nin üst öğesi olduğunu ekledim, bu şekilde Activity-B'nin ActionBar'ında bir geri veya home düğmesi var
Sorun: Activity-B ActionBar'da Back veya Home düğmesine her bastığımda Activity-A, onCreate () 'den başlayarak tam Activity Life Cycle'a giriyor
RecyclerView Bağdaştırıcısının Listesini kaydederek bir onSaveInstanceState () uygulamama rağmen, Activity-A yaşam döngüsünü başlattığında, saveInstanceState onCreate () yönteminde her zaman boştur.
İnternette daha fazla araştırma yaparken aynı problemle karşılaştım ancak kişi Anroid cihazının altındaki Geri veya Ana Sayfa düğmesinin (Varsayılan Geri / Ana Sayfa düğmesi) Activity-A'nın Aktivite Yaşam Döngüsüne girmediğini fark etti.
Çözüm:
Activity-B'de ana sayfa veya geri düğmesini etkinleştirdim
OnCreate () yöntemi altında bu satırı ekleyin supportActionBar? .SetDisplayHomeAsUpEnabled (true)
Overide fun onOptionsItemSelected () yönteminde, id'ye göre hangi öğenin tıklandığı item.ItemId öğesini kontrol ettim. Geri düğmesinin kimliği:
android.R.id.home
Ardından android.R.id.home içinde bir finish () işlevi çağrısı uygulayın
Bu, Aktivite-B'yi sona erdirecek ve tüm yaşam döngüsünden geçmeden Acitivy-A'yı getirecektir.
İhtiyacım için bu şimdiye kadar en iyi çözüm.
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)
}
Küçük bir farkla aynı sorunu yaşadım, Veri Bağlama kullanıyordum ve ben recyclerView adaptörü ve bağlayıcı yöntemlerde LayoutManager batıyordu.
Sorun, onResume'dan sonra veri bağlamanın gerçekleşmesiydi, bu yüzden onResume'dan sonra layoutManager'ımı sıfırlıyordu ve bu, her seferinde orijinal yerine geri kaydırıldığı anlamına geliyordu. Databinding Adapter yöntemlerinde layoutManager'ı ayarlamayı bıraktığımda tekrar iyi çalışıyor.
Benim durumumda RecyclerView’ları layoutManager
hem XML’de hem de onViewCreated
. Görevi kaldırmak onViewCreated
onu düzeltti.
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();
}
Neden kullanıyorum OnSaveInstanceState()
içinde onPause()
başka bir etkinliğe anahtarı OnPause called.It o kaydırma konumunu kaydetmek ve geri başka bir etkinliğe gelen pozisyonunu geri yükleyecektir olurdu da, aracı.