Ho una domanda su RecyclerView.State di Android .
Sto usando RecyclerView, come posso usarlo e collegarlo a RecyclerView.State?
Il mio scopo è salvare la posizione di scorrimento di RecyclerView .
Ho una domanda su RecyclerView.State di Android .
Sto usando RecyclerView, come posso usarlo e collegarlo a RecyclerView.State?
Il mio scopo è salvare la posizione di scorrimento di RecyclerView .
Risposte:
Come pensi di salvare l'ultima posizione salvata con RecyclerView.State
?
Puoi sempre fare affidamento sul vecchio stato di salvataggio. Estendi RecyclerView
e sovrascrivi onSaveInstanceState () e 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];
}
};
}
A partire dal recyclerview:1.2.0-alpha02
rilascio StateRestorationPolicy
è stato introdotto. Potrebbe essere un approccio migliore al problema dato.
Questo argomento è stato trattato nell'articolo medio degli sviluppatori Android .
Inoltre, @ rubén-viguera ha condiviso maggiori dettagli nella risposta di seguito. https://stackoverflow.com/a/61609823/892500
Se stai usando LinearLayoutManager, viene fornito con l'api di salvataggio pre-costruito linearLayoutManagerInstance.onSaveInstanceState () e ripristina l'api linearLayoutManagerInstance.onRestoreInstanceState (...)
Con ciò, puoi salvare il pacco restituito nel tuo stato esterno. per esempio,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
e ripristina la posizione di ripristino con lo stato salvato. per esempio,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Per concludere, il tuo codice finale sarà simile a
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());
}
Modificare: puoi anche utilizzare le stesse API con GridLayoutManager, poiché è una sottoclasse di LinearLayoutManager. Grazie @wegsehen per il suggerimento.
Modifica: ricorda, se stai caricando anche i dati in un thread in background, dovrai chiamare onRestoreInstanceState all'interno del tuo metodo onPostExecute / onLoadFinished affinché la posizione venga ripristinata al cambio di orientamento, ad es.
@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
ma non se hai un ViewPager
con RecycerView
su ogni pagina.
onCreateView
, ma dopo il caricamento dei dati. Vedi la risposta di @Farmaker qui.
Negozio
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Ristabilire
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
e se non funziona, prova
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Metti da parte onPause()
e ripristinaonResume()
lastFirstVisiblePosition
su 0
dopo averlo ripristinato. Questo caso è utile quando hai un frammento / un'attività che carica dati diversi in uno RecyclerView
, ad esempio un'app di esplorazione di file.
SwipeRefreshLayout
?
RecyclerView
Volevo salvare la posizione di scorrimento di Recycler View quando ci si allontana dalla mia attività dell'elenco e quindi facendo clic sul pulsante Indietro per tornare indietro. Molte delle soluzioni fornite per questo problema erano molto più complicate del necessario o non funzionavano per la mia configurazione, quindi ho pensato di condividere la mia soluzione.
Per prima cosa salva lo stato dell'istanza in onPause come molti hanno mostrato. Penso che valga la pena sottolineare qui che questa versione di onSaveInstanceState è un metodo della classe RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
La chiave per farlo funzionare correttamente è assicurarsi di chiamare onRestoreInstanceState dopo aver collegato l'adattatore, come alcuni hanno indicato in altri thread. Tuttavia, l'attuale chiamata al metodo è molto più semplice di quanto molti hanno indicato.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
Impostare le variabili in onCreate (), salvare la posizione di scorrimento in onPause () e impostare la posizione di scorrimento in 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);
}
}
Non devi più salvare e ripristinare lo stato da solo. Se imposti l'ID univoco in xml e recyclerView.setSaveEnabled (true) (true per impostazione predefinita) il sistema lo farà automaticamente. Ecco di più su questo: http://trickyandroid.com/saving-android-view-state-correctly/
Tutti i gestori di layout raggruppati nella libreria di supporto sanno già come salvare e ripristinare la posizione di scorrimento.
La posizione di scorrimento viene ripristinata al primo passaggio di layout che si verifica quando vengono soddisfatte le seguenti condizioni:
RecyclerView
RecyclerView
In genere imposti il gestore del layout in XML, quindi tutto ciò che devi fare ora è
RecyclerView
Puoi farlo in qualsiasi momento, non è vincolato a Fragment.onCreateView
o Activity.onCreate
.
Esempio: assicurarsi che l'adattatore sia collegato ogni volta che i dati vengono aggiornati.
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
}
}
La posizione di scorrimento salvata viene calcolata dai seguenti dati:
Pertanto puoi aspettarti una leggera discrepanza, ad esempio se i tuoi articoli hanno dimensioni diverse in orientamento verticale e orizzontale.
Il RecyclerView
/ ScrollView
/ qualunque cosa deve avere un android:id
perché il suo stato venga salvato.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Ecco il mio approccio per salvarli e mantenere lo stato di riciclovisualizzandoli in un frammento. Innanzitutto, aggiungi le modifiche alla configurazione nella tua attività genitore come sotto il codice.
android:configChanges="orientation|screenSize"
Quindi nel tuo frammento dichiara questi metodi di istanza
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Aggiungi sotto il codice nel tuo metodo @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);
}
Aggiungi il metodo onConfigartionChanged come di seguito.
@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);
}
Questo approccio risolverà il tuo problema. se hai un gestore di layout diverso, sostituisci semplicemente il gestore di layout superiore.
Ecco come ripristino la posizione di RecyclerView con GridLayoutManager dopo la rotazione quando è necessario ricaricare i dati da Internet con AsyncTaskLoader.
Crea una variabile globale di Parcelable e GridLayoutManager e una stringa finale statica:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Salva lo stato di gridLayoutManager in onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Ripristina in 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);
}
}
Quindi, quando il caricatore recupera i dati da Internet, ripristini la posizione di Recyclerview in onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. Ovviamente devi istanziare gridLayoutManager all'interno di onCreate. Saluti
Ho avuto lo stesso requisito, ma le soluzioni qui non mi hanno portato a termine, a causa della fonte dei dati per recyclerView.
Stavo estraendo lo stato LinearLayoutManager di RecyclerViews in onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
viene salvato in onSaveInstanceState(Bundle outState)
ed estratto nuovamente in onCreate(Bundle savedInstanceState)
, when savedInstanceState != null
.
Tuttavia, l'adattatore recyclerView viene popolato e aggiornato da un oggetto ViewModel LiveData restituito da una chiamata DAO a un 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);
}
});
Alla fine ho scoperto che il ripristino dello stato dell'istanza direttamente dopo che i dati sono stati impostati nell'adattatore manterrebbe lo stato di scorrimento attraverso le rotazioni.
Sospetto che questo sia dovuto al fatto che il file LinearLayoutManager
stato che avevo ripristinato veniva sovrascritto quando i dati sono stati restituiti dalla chiamata al database, oppure lo stato ripristinato era privo di significato rispetto a un LinearLayoutManager "vuoto".
Se i dati dell'adattatore sono disponibili direttamente (ovvero non contigenti su una chiamata al database), il ripristino dello stato dell'istanza su LinearLayoutManager può essere eseguito dopo che l'adattatore è stato impostato su recyclerView.
La distinzione tra i due scenari mi ha tenuto duro per secoli.
Su Android API Level 28, mi assicuro semplicemente di aver impostato il mio LinearLayoutManager
e RecyclerView.Adapter
nel mio Fragment#onCreateView
metodo e tutto Just Worked ™ ️. Non avevo bisogno di fare niente onSaveInstanceState
o onRestoreInstanceState
lavorare.
La risposta di Eugen Pechanec spiega perché funziona.
A partire dalla versione 1.2.0-alpha02 della libreria androidx recyclerView, ora è gestita automaticamente. Aggiungilo semplicemente con:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
E usa:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
L'enumerazione StateRestorationPolicy ha 3 opzioni:
Si noti che al momento di questa risposta, la libreria recyclerView è ancora in alpha03, ma la fase alpha non è adatta per scopi di produzione .
Per me, il problema era che impostavo un nuovo layoutmanager ogni volta che cambiavo l'adattatore, perdendo la posizione di scorrimento della coda su recyclerView.
Vorrei solo condividere la recente situazione difficile che ho incontrato con RecyclerView. Spero che chiunque abbia lo stesso problema ne trarrà beneficio.
Requisiti del mio progetto: quindi ho un RecyclerView che elenca alcuni elementi cliccabili nella mia attività principale (Activity-A). Quando si fa clic sull'articolo, viene visualizzata una nuova attività con i dettagli dell'elemento (Attività-B).
Ho implementato nel Manifest file che Activity-A è il genitore di Activity-B, in questo modo, ho un pulsante indietro o home sulla ActionBar dell'Activity-B
Problema: ogni volta che si preme il pulsante Indietro o Home nella barra delle azioni dell'Attività-B, l'Attività-A entra nel ciclo di vita completo dell'attività a partire da onCreate ()
Anche se ho implementato un onSaveInstanceState () salvando l'elenco dell'adattatore di RecyclerView, quando Activity-A avvia il suo ciclo di vita, saveInstanceState è sempre nullo nel metodo onCreate ().
Scavando ulteriormente in Internet, mi sono imbattuto nello stesso problema ma la persona ha notato che il pulsante Indietro o Home sotto il dispositivo Anroid (pulsante Indietro / Home predefinito), l'Attività-A non entra nel ciclo di vita dell'attività.
Soluzione:
Ho abilitato il pulsante Home o Indietro su Activity-B
Sotto il metodo onCreate () aggiungi questa riga supportActionBar? .SetDisplayHomeAsUpEnabled (true)
Nel divertente metodo onOptionsItemSelected (), ho controllato item.ItemId su quale elemento viene cliccato in base all'id. L'ID del pulsante Indietro è
android.R.id.home
Quindi implementare una chiamata alla funzione finish () all'interno di android.R.id.home
Questo terminerà l'Attività-B e porterà l'Attività-A senza passare attraverso l'intero ciclo di vita.
Per le mie esigenze questa è la migliore soluzione finora.
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)
}
Ho avuto lo stesso problema con una piccola differenza, stavo usando l' associazione dati e stavo impostando l'adattatore recyclerView e il layoutManager nei metodi di associazione.
Il problema era che l'associazione dei dati stava avvenendo dopo onResume, quindi stava ripristinando il mio layoutManager dopo onResume e ciò significa che stava scorrendo indietro alla posizione originale ogni volta. Quindi, quando ho smesso di impostare layoutManager nei metodi dell'adattatore Databinding, funziona di nuovo bene.
Nel mio caso stavo impostando RecyclerView layoutManager
sia in XML che in onViewCreated
. La rimozione dell'assegnazione è stata onViewCreated
risolta.
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();
}
Perché sto usando OnSaveInstanceState()
in onPause()
significa, mentre passa a un'altra attività in Pausa verrebbe chiamato. Salverà quella posizione di scorrimento e ripristinerà la posizione quando torneremo da un'altra attività.