Ich habe eine Frage zu Android RecyclerView.State .
Ich verwende ein RecyclerView. Wie kann ich es verwenden und mit RecyclerView.State binden?
Mein Zweck ist es , die Bildlaufposition von RecyclerView zu speichern .
Ich habe eine Frage zu Android RecyclerView.State .
Ich verwende ein RecyclerView. Wie kann ich es verwenden und mit RecyclerView.State binden?
Mein Zweck ist es , die Bildlaufposition von RecyclerView zu speichern .
Antworten:
Wie wollen Sie die zuletzt gespeicherte Position mit speichern? RecyclerView.State
?
Sie können sich immer auf den guten Speicherzustand verlassen. Erweitern RecyclerView
und überschreiben Sie onSaveInstanceState () und 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];
}
};
}
Ab der recyclerview:1.2.0-alpha02
VeröffentlichungStateRestorationPolicy
wurde eingeführt. Es könnte eine bessere Herangehensweise an das gegebene Problem sein.
Dieses Thema wurde in einem mittleren Artikel für Android-Entwickler behandelt .
Außerdem hat @ rubén-viguera in der folgenden Antwort weitere Details mitgeteilt. https://stackoverflow.com/a/61609823/892500
Wenn Sie LinearLayoutManager verwenden, wird die vorgefertigte API zum Speichern von linearLayoutManagerInstance.onSaveInstanceState () und zum Wiederherstellen der API linearLayoutManagerInstance.onRestoreInstanceState (...) mitgeliefert.
Damit können Sie das zurückgegebene Paket in Ihrem Außenstaat speichern. z.B,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
und stellen Sie die Wiederherstellungsposition mit dem von Ihnen gespeicherten Status wieder her. z.B,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Zum Abschluss sieht Ihr endgültiger Code ungefähr so aus
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());
}
Bearbeiten: Sie können dieselbe API auch mit dem GridLayoutManager verwenden, da es sich um eine Unterklasse von LinearLayoutManager handelt. Danke @wegsehen für den Vorschlag.
Bearbeiten: Denken Sie daran, dass Sie, wenn Sie auch Daten in einen Hintergrundthread laden, onRestoreInstanceState in Ihrer onPostExecute / onLoadFinished-Methode aufrufen müssen, damit die Position bei einer Änderung der Ausrichtung wiederhergestellt werden kann, z
@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
aber nicht, wenn Sie auf jeder Seite eine ViewPager
mit einer RecycerView
haben.
onCreateView
, sondern nach dem Laden der Daten. Siehe @ Farmaker Antwort hier.
Geschäft
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Wiederherstellen
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
und wenn das nicht funktioniert, versuchen Sie es
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Speichern onPause()
und wiederherstellenonResume()
lastFirstVisiblePosition
, 0
nachdem Sie es wiederhergestellt haben. Dieser Fall ist nützlich, wenn Sie ein Fragment / eine Aktivität haben, die verschiedene Daten in eine lädt RecyclerView
, z. B. eine Datei-Explorer-App.
SwipeRefreshLayout
?
RecyclerView
Ich wollte die Bildlaufposition von Recycler View speichern, wenn ich von meiner Listenaktivität weg navigiere und dann auf die Schaltfläche "Zurück" klicke, um zurück zu navigieren. Viele der für dieses Problem bereitgestellten Lösungen waren entweder viel komplizierter als erforderlich oder funktionierten für meine Konfiguration nicht. Daher dachte ich, ich würde meine Lösung teilen.
Speichern Sie zuerst Ihren Instanzstatus in onPause, wie viele gezeigt haben. Ich denke, es ist hier hervorzuheben, dass diese Version von onSaveInstanceState eine Methode aus der RecyclerView.LayoutManager-Klasse ist.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Der Schlüssel, damit dies ordnungsgemäß funktioniert, besteht darin, sicherzustellen, dass Sie onRestoreInstanceState aufrufen, nachdem Sie Ihren Adapter angeschlossen haben, wie einige in anderen Threads angegeben haben. Der eigentliche Methodenaufruf ist jedoch viel einfacher als von vielen angegeben.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
I Setzen Sie Variablen in onCreate (), speichern Sie die Bildlaufposition in onPause () und setzen Sie die Bildlaufposition 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);
}
}
Sie müssen den Status nicht mehr selbst speichern und wiederherstellen. Wenn Sie eine eindeutige ID in XML festlegen und recyclerView.setSaveEnabled (true) (standardmäßig true), wird dies vom System automatisch ausgeführt. Hier ist mehr dazu: http://trickyandroid.com/saving-android-view-state-correctly/
Alle in der Support-Bibliothek enthaltenen Layout-Manager wissen bereits, wie die Bildlaufposition gespeichert und wiederhergestellt wird.
Die Bildlaufposition wird beim ersten Layoutdurchlauf wiederhergestellt, wenn die folgenden Bedingungen erfüllt sind:
RecyclerView
RecyclerView
In der Regel legen Sie den Layout-Manager in XML fest, sodass Sie jetzt nur noch etwas tun müssen
RecyclerView
Sie können dies jederzeit tun, es ist nicht auf Fragment.onCreateView
oder beschränktActivity.onCreate
.
Beispiel: Stellen Sie sicher, dass der Adapter bei jeder Aktualisierung der Daten angeschlossen ist.
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
}
}
Die gespeicherte Bildlaufposition wird aus folgenden Daten berechnet:
Daher können Sie leichte Abweichungen erwarten, z. B. wenn Ihre Artikel im Hoch- und Querformat unterschiedliche Abmessungen haben.
Das RecyclerView
/ ScrollView
/ was auch immer muss ein haben, android:id
damit sein Zustand gespeichert wird.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Hier ist mein Ansatz, um sie zu speichern und den Status der Recyclingübersicht in einem Fragment beizubehalten. Fügen Sie zunächst Konfigurationsänderungen in Ihrer übergeordneten Aktivität wie im folgenden Code hinzu.
android:configChanges="orientation|screenSize"
Deklarieren Sie dann in Ihrem Fragment diese Instanzmethoden
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Fügen Sie den folgenden Code in Ihre @ onPause-Methode ein
@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);
}
Fügen Sie die onConfigartionChanged-Methode wie unten hinzu.
@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);
}
Dieser Ansatz wird Ihr Problem beheben. Wenn Sie einen anderen Layout-Manager haben, ersetzen Sie einfach den oberen Layout-Manager.
Auf diese Weise stelle ich die RecyclerView-Position mit GridLayoutManager nach der Drehung wieder her, wenn Sie mit AsyncTaskLoader Daten aus dem Internet neu laden müssen.
Erstellen Sie eine globale Variable aus Parcelable und GridLayoutManager sowie eine statische endgültige Zeichenfolge:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Speichern Sie den Status von gridLayoutManager in onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
In onRestoreInstanceState wiederherstellen
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//restore recycler view at same position
if (savedInstanceState != null) {
savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
}
Wenn der Loader Daten aus dem Internet abruft, stellen Sie die Position der Recycling-Ansicht in onLoadFinished () wieder her.
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
..natürlich müssen Sie gridLayoutManager in onCreate instanziieren. Prost
Ich hatte die gleichen Anforderungen, aber die Lösungen hier haben mich aufgrund der Datenquelle für die recyclerView nicht ganz über die Linie gebracht.
Ich habe den LinearLayoutManager-Status von RecyclerViews in onPause () extrahiert.
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Das Paket state
wird gespeichert onSaveInstanceState(Bundle outState)
und erneut extrahiert onCreate(Bundle savedInstanceState)
, wennsavedInstanceState != null
.
Der recyclerView-Adapter wird jedoch von einem ViewModel LiveData-Objekt gefüllt und aktualisiert, das von einem DAO-Aufruf an eine Room-Datenbank zurückgegeben wird.
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
Ich fand schließlich heraus, dass das Wiederherstellen des Instanzstatus direkt nach dem Festlegen der Daten im Adapter den Bildlaufstatus über Rotationen hinweg beibehalten würde.
Ich vermute, das liegt auch daran, dass die LinearLayoutManager
dass der wiederhergestellte Status überschrieben wurde, als die Daten vom Datenbankaufruf zurückgegeben wurden, oder dass der wiederhergestellte Status für einen 'leeren' LinearLayoutManager bedeutungslos war.
Wenn die Adapterdaten direkt verfügbar sind (dh bei einem Datenbankaufruf nicht relevant sind), kann der Instanzstatus im LinearLayoutManager wiederhergestellt werden, nachdem der Adapter in der recyclerView festgelegt wurde.
Die Unterscheidung zwischen den beiden Szenarien hat mich ewig aufgehalten.
Auf der Android-API-Ebene 28 stelle ich einfach sicher, dass ich meine LinearLayoutManager
und RecyclerView.Adapter
meine Fragment#onCreateView
Methode sowie alles, was gerade funktioniert hat, eingerichtet habe. Ich brauche nicht zu tun onSaveInstanceState
oder zu onRestoreInstanceState
arbeiten.
Die Antwort von Eugen Pechanec erklärt, warum dies funktioniert.
Ab Version 1.2.0-alpha02 der androidx recyclerView-Bibliothek wird diese nun automatisch verwaltet. Fügen Sie es einfach hinzu mit:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
Und benutze:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
Die Aufzählung StateRestorationPolicy bietet drei Optionen:
Beachten Sie, dass sich die recyclerView-Bibliothek zum Zeitpunkt dieser Antwort noch in alpha03 befindet, die alpha-Phase jedoch nicht für Produktionszwecke geeignet ist .
Für mich bestand das Problem darin, dass ich jedes Mal, wenn ich meinen Adapter wechselte, einen neuen Layoutmanager einrichtete und die Position des Bildlaufs in recyclerView verlor.
Ich möchte nur die jüngste Situation mit RecyclerView teilen. Ich hoffe, dass jeder, der das gleiche Problem hat, davon profitieren wird.
Meine Projektanforderung: Ich habe also eine RecyclerView, in der einige anklickbare Elemente in meiner Hauptaktivität (Aktivität A) aufgelistet sind. Wenn Sie auf den Artikel klicken, wird eine neue Aktivität mit den Artikeldetails (Aktivität-B) angezeigt.
Ich habe im Manifest implementiert Datei dass Aktivität-A das übergeordnete Element von Aktivität-B ist. Auf diese Weise habe ich eine Zurück- oder Startschaltfläche in der Aktionsleiste von Aktivität-B
Problem: Jedes Mal, wenn ich die Zurück- oder Home-Taste in der Aktionsleiste von Aktivität B gedrückt habe, wechselt Aktivität A ab onCreate () in den vollständigen Aktivitätslebenszyklus.
Obwohl ich ein onSaveInstanceState () implementiert habe, das die Liste des RecyclerView-Adapters speichert, ist saveInstanceState in der onCreate () -Methode immer null, wenn Activity-A seinen Lebenszyklus startet.
Beim weiteren Durchsuchen des Internets stieß ich auf das gleiche Problem, aber die Person bemerkte, dass die Schaltfläche Zurück oder Startseite unter dem Anroid-Gerät (Standard-Schaltfläche Zurück / Startseite), Aktivität-A, nicht in den Aktivitätslebenszyklus eingeht.
Lösung:
Ich habe die Home- oder Back-Taste in Aktivität B aktiviert
Fügen Sie unter der Methode onCreate () diese Zeile hinzu. SupportActionBar? .SetDisplayHomeAsUpEnabled (true)
Bei der Methode overide fun onOptionsItemSelected () habe ich nach dem Element gesucht. Element-ID, auf die das Element basierend auf der ID geklickt wird. Die ID für die Zurück-Schaltfläche lautet
android.R.id.home
Implementieren Sie dann einen Funktionsaufruf finish () im android.R.id.home
Dies beendet die Aktivität B und bringt Aktivität A, ohne den gesamten Lebenszyklus zu durchlaufen.
Für meine Anforderung ist dies die bisher beste Lösung.
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)
}
Ich hatte das gleiche Problem mit einem kleinen Unterschied, ich wurde mit Databinding , und ich war recyclerView Adapter und Layoutmanager in den Bindeverfahren setzen.
Das Problem war , dass die Daten-Bindung wurde nach geschieht onResume so war es mein Layout nach onResume Zurücksetzen und das bedeutet , es wurde jedes Mal zurück zum ursprünglichen Platz zu scrollen. Wenn ich die Einstellung von layoutManager in den Datenbindungsadaptermethoden beendet habe, funktioniert dies wieder einwandfrei.
In meinem Fall habe ich die RecyclerViews layoutManager
sowohl in XML als auch in festgelegt onViewCreated
. Das Entfernen der Zuordnung wurde onViewCreated
behoben.
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();
}
Warum ich OnSaveInstanceState()
in onPause()
Mitteln verwende, während der Wechsel zu einer anderen Aktivität onPause aufgerufen wird. Diese Bildlaufposition wird gespeichert und die Position wiederhergestellt, wenn wir von einer anderen Aktivität zurückkehren.