wake-up-neo.com

So speichern Sie die Bildlaufposition von RecyclerView mithilfe von RecyclerView.State

Ich habe eine Frage zu Android's RecyclerView.State .

Ich verwende ein RecyclerView. Wie kann ich es mit RecyclerView.State verwenden und daran binden?

Mein Zweck ist es, die Scrollposition des RecyclerView zu speichern.

85
陈文涛

Wie planen Sie die letzte gespeicherte Position mit RecyclerView.State zu speichern?

Sie können sich immer auf den guten Zustand verlassen. RecyclerView erweitern und onSaveInstanceState () und onRestoreInstanceState() überschreiben:

    @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.getChildCount();
              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];
            }
        };
    }
32

Wenn Sie LinearLayoutManager verwenden, wird der vorinstallierte Speicher api linearLayoutManagerInstance.onSaveInstanceState () und Wiederherstellen von api linearLayoutManagerInstance.onRestoreInstanceState (...)

Damit können Sie das zurückgegebene Paket in Ihrem outState 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);

Alles in allem sieht der endgültige Code in etwa 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());
}

Edit: Sie können mit dem GridLayoutManager auch dieselbe API verwenden, da es sich um eine Unterklasse von LinearLayoutManager handelt. Danke @wegsehen für den Vorschlag.

Edit: Wenn Sie auch Daten in einen Hintergrundthread laden, müssen Sie onRestoreInstanceState innerhalb Ihrer onPostExecute/onLoadFinished-Methode aufrufen, damit die Position bei einer Orientierungsänderung wiederhergestellt wird, 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();
        }
    }
181

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)

Speichere in onPause() Und stelle in onResume() wieder

66
Gillis Haasnoot

I Variablen in onCreate () setzen, Bildlaufposition in onPause () speichern und Bildlaufposition in onResume () festlegen

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);
    }
}
16
farhad.kargaran

Ich wollte die Bildlaufposition der Recycler-Ansicht speichern, wenn Sie von meiner Listenaktivität weg navigieren und dann auf die Zurück-Schaltfläche klicken, um zurück zu navigieren. Viele der für dieses Problem bereitgestellten Lösungen waren entweder viel komplizierter als nötig oder funktionierten nicht für meine Konfiguration. Daher dachte ich, ich würde meine Lösung teilen.

Speichern Sie zunächst Ihren Instanzstatus in onPause, wie viele gezeigt haben. Ich denke, es lohnt sich zu betonen, 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();
        }

Um dies richtig zu machen, müssen Sie sicherstellen, dass Sie onRestoreInstanceState aufrufen, nachdem Sie Ihren Adapter angeschlossen haben, wie in anderen Threads bereits angegeben wurde. Der eigentliche Methodenaufruf ist jedoch viel einfacher als von vielen angegeben.

private void someMethod() {
    mVenueRecyclerView.setAdapter(mVenueAdapter);
    mLayoutManager.onRestoreInstanceState(state);
}
13
Braden Holt

Sie müssen den Zustand nicht mehr selbst speichern und wiederherstellen. Wenn Sie eine eindeutige ID in xml festlegen und recyclerView.setSaveEnabled (true) (standardmäßig true), wird das System dies automatisch tun . Hier ist mehr dazu: http://trickyandroid.com/saving-Android-view- Zustand korrekt/

6
AppiDevo

So stelle ich die RecyclerView-Position nach dem Drehen mit GridLayoutManager wieder her, wenn Sie Daten mit AsyncTaskLoader aus dem Internet neu laden müssen.

Erstellen Sie eine globale Variable aus Parcelable und GridLayoutManager sowie eine statische abschließende Zeichenfolge:

private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";

Status von gridLayoutManager in onSaveInstance () speichern

 @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, 
            mGridLayoutManager.onSaveInstanceState());
        }

Wiederherstellen 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);
        }
    }

Wenn der Loader dann Daten aus dem Internet abruft, stellen Sie die Recycling-Position in onLoadFinished () wieder her.

if(savedRecyclerLayoutState!=null){
                mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
            }

.. Natürlich müssen Sie gridLayoutManager in onCreate . Prost instanziieren

3
Farmaker

Hier ist mein Ansatz, um sie zu speichern und den Status des Recyclings in einem Fragment beizubehalten. Zuerst fügen Sie Konfigurationsänderungen in Ihrer übergeordneten Aktivität als Code hinzu.

Android:configChanges="orientation|screenSize"

Dann deklarieren Sie in Ihrem Fragment diese Instanzmethoden

private  Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;

Fügen Sie unten in Ihrer @onPause-Methode Code hinzu

@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 wie unten beschrieben eine onConfigartionChanged-Methode 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 wieder lösen. Wenn Sie einen anderen Layout-Manager haben, ersetzen Sie einfach den oberen Layout-Manager.

2

Alle in der Support-Bibliothek gebündelten Layout-Manager wissen bereits, wie sie die Bildlaufposition speichern und wiederherstellen.

Die Bildlaufposition wird beim ersten Layoutdurchlauf wiederhergestellt. Dies geschieht, wenn die folgenden Bedingungen erfüllt sind:

  • ein Layout-Manager ist an RecyclerView angehängt.
  • ein Adpater ist an die RecyclerView angehängt

Normalerweise setzen Sie den Layout-Manager in XML, sodass Sie jetzt nur noch etwas tun müssen 

  1. Laden Sie den Adapter mit Daten
  2. Dann schließen Sie den Adapter an RecyclerView an.

Sie können dies jederzeit tun, es ist nicht auf Fragment.onCreateView oder Activity.onCreate beschränkt.

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:

  • Adapterposition des ersten Elements auf dem Bildschirm
  • Pixelversatz vom oberen Rand des Elements vom oberen Rand der Liste (für vertikales Layout)

Daher können Sie leichte Abweichungen erwarten, z. wenn Ihre Artikel unterschiedliche Abmessungen in Hoch- und Querformat haben.

1
Eugen Pechanec

Auf Android API Level 28 stelle ich einfach sicher, dass ich meine LinearLayoutManager und RecyclerView.Adapter in meiner Fragment#onCreateView-Methode und alles Just Worked ™ eingerichtet habe. Ich musste keine onSaveInstanceState- oder onRestoreInstanceState-Arbeit ausführen.

Eugen Pechanecs Antwort erklärt, warum das funktioniert.

0
Heath Borders

Für mich bestand das Problem darin, dass ich jedes Mal einen neuen Layoutmanager eingerichtet habe, wenn ich meinen Adapter geändert habe, wodurch die Bildlaufposition von Queer in RecyclerView verloren ging.

0

Ich hatte die gleiche Anforderung, aber die Lösungen hier haben mich aufgrund der Datenquelle für die RecyclerView-Version nicht so richtig verstanden.

Ich habe den RecyclerViews-Status LinearLayoutManager in onPause () extrahiert.

private Parcelable state;

Public void onPause() {
    state = mLinearLayoutManager.onSaveInstanceState();
}

Parcelable state wird in onSaveInstanceState(Bundle outState) gespeichert und erneut in onCreate(Bundle savedInstanceState) extrahiert, wenn savedInstanceState != 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, dass die Wiederherstellung des Instanzstatus direkt nach dem Festlegen der Daten im Adapter den Scroll-Status über Rotationen hinweg beibehalten würde.

Ich vermute, dass dies entweder darauf zurückzuführen ist, dass der von mir wiederhergestellte Zustand LinearLayoutManager überschrieben wurde, als die Daten vom Datenbankaufruf zurückgegeben wurden, oder dass der wiederhergestellte Zustand für einen 'leeren' LinearLayoutManager bedeutungslos war.

Wenn die Adapterdaten direkt verfügbar sind (dh bei einem Datenbankaufruf nicht verfügbar sind), kann das Wiederherstellen des Instanzstatus auf dem LinearLayoutManager durchgeführt werden, nachdem der Adapter in der RecyclerView festgelegt wurde.

Die Unterscheidung zwischen den beiden Szenarien hielt mich lange Zeit aufrecht.

0
MikeP

Ich möchte nur die jüngsten Schwierigkeiten, auf die ich stoße, mit RecyclerView teilen. Ich hoffe, dass jeder, der das gleiche Problem hat, davon profitieren wird.

Meine Projektanforderung: Daher habe ich eine RecyclerView, die einige anklickbare Elemente in meiner Hauptaktivität (Aktivität-A) aufführt. Wenn Sie auf das Element klicken, wird eine neue Aktivität mit den Artikeldetails (Aktivität-B) angezeigt.

Ich habe im Manifest die Datei implementiert, dass die Aktivität-A das übergeordnete Element von Aktivität-B ist. Auf diese Weise habe ich auf der Aktionsleiste der Aktivität-B einen Zurück- oder Home-Button

Problem: Jedes Mal, wenn ich in der Activity-B-Aktionsleiste die Zurück- oder die Home-Taste drückte, geht die Activity-A in den vollständigen Aktivitätslebenszyklus ein, beginnend mit onCreate ().

Auch wenn ich ein onSaveInstanceState () -Parameter implementiert habe, um die Liste des RecyclerView-Adapters zu speichern. Wenn Activity-A seinen Lebenszyklus startet, ist saveInstanceState in der onCreate () - Methode immer null.

Beim Graben im Internet stieß ich auf das gleiche Problem, aber die Person bemerkte, dass die Schaltfläche Zurück oder Home unter dem Anroid-Gerät (Standard-Schaltfläche Zurück/Home) die Aktivität-A nicht in den Aktivitätslebenszyklus eintritt.

Lösung:

  1. Ich habe das Tag für übergeordnete Aktivität im Manifest für Aktivität-B entfernt
  2. Ich habe die Home- oder Zurück-Schaltfläche in Activity-B aktiviert

    Fügen Sie unter der onCreate () - Methode diese Zeile hinzu supportActionBar? .SetDisplayHomeAsUpEnabled (true)

  3. In der overide fun onOptionsItemSelected () -Methode habe ich nach item.ItemId gesucht, auf das Element basierend auf der ID geklickt wird. Die ID für die Zurück-Schaltfläche lautet 

    Android.R.id.home

    Implementieren Sie anschließend einen finish () -Funktionsaufruf im Android.R.id.home

    Dies beendet die Aktivität-B und führt zu Aktivitäts-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)
}
0
Andy Parinas

Ich hatte das gleiche Problem mit einem kleinen Unterschied, ich verwendete Datenbindung und ich stellte den recyclerView-Adapter und den layoutManager in den Bindungsmethoden ein.

Das Problem war, dass die Datenbindung nach onResume stattfand, so dass mein layoutManager nach onResume zurückgesetzt wurde und das bedeutet, dass jedes Mal zum ursprünglichen Ort zurückgescrollt wurde. Als ich also aufgehört habe, layoutManager in den Datenbindungsadaptermethoden einzustellen, funktioniert es wieder einwandfrei.

0