Mam pytanie dotyczące programu RecyclerView.State w systemie Android .
Używam RecyclerView, jak mogę go użyć i powiązać z RecyclerView.State?
Moim celem jest zapisanie pozycji przewijania RecyclerView .
Mam pytanie dotyczące programu RecyclerView.State w systemie Android .
Używam RecyclerView, jak mogę go użyć i powiązać z RecyclerView.State?
Moim celem jest zapisanie pozycji przewijania RecyclerView .
Odpowiedzi:
Jak planujesz zapisać ostatnią zapisaną pozycję RecyclerView.State
?
Zawsze możesz polegać na starym dobrym stanie zapisu. Rozszerz RecyclerView
i zastąp onSaveInstanceState () i 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];
}
};
}
Począwszy od recyclerview:1.2.0-alpha02
wydania StateRestorationPolicy
zostało wprowadzone. To mogłoby być lepsze podejście do danego problemu.
Ten temat został omówiony w średnim artykule dla programistów Androida .
Ponadto @ rubén-viguera udostępnił więcej szczegółów w odpowiedzi poniżej. https://stackoverflow.com/a/61609823/892500
Jeśli używasz LinearLayoutManager, jest dostarczany z gotowym zapisem API linearLayoutManagerInstance.onSaveInstanceState () i przywracaniem interfejsu API linearLayoutManagerInstance.onRestoreInstanceState (...)
Dzięki temu możesz zapisać zwróconą paczkę w swoim OutState. na przykład,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
i przywróć pozycję przywracania z zapisanym stanem. na przykład,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Podsumowując, ostateczny kod będzie wyglądał mniej więcej tak
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());
}
Edycja: tego samego interfejsu API można również używać z GridLayoutManager, ponieważ jest to podklasa LinearLayoutManager. Dzięki @wegsehen za sugestię.
Edycja: Pamiętaj, że jeśli ładujesz również dane w wątku w tle, będziesz musiał wywołać onRestoreInstanceState w ramach metody onPostExecute / onLoadFinished, aby pozycja została przywrócona po zmianie orientacji, np.
@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
, ale jeśli nie masz ViewPager
z RecycerView
każdej strony.
onCreateView
, ale po załadowaniu danych. Zobacz odpowiedź @Farmaker tutaj.
Sklep
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Przywracać
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
a jeśli to nie zadziała, spróbuj
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Włóż sklep onPause()
i przywróćonResume()
lastFirstVisiblePosition
do 0
. Ten przypadek jest przydatny, gdy masz fragment / działanie, które ładuje różne dane w jednym RecyclerView
, na przykład w aplikacji eksploratora plików.
SwipeRefreshLayout
?
RecyclerView
Chciałem zapisać pozycję przewijania Recycler View podczas opuszczania mojej listy aktywności, a następnie kliknięcia przycisku Wstecz, aby przejść wstecz. Wiele rozwiązań przedstawionych dla tego problemu było albo o wiele bardziej skomplikowanych niż było to konieczne, lub nie działało w mojej konfiguracji, więc pomyślałem, że podzielę się swoim rozwiązaniem.
Najpierw zapisz stan instancji w onPause, jak wiele osób pokazało. Myślę, że warto tu podkreślić, że ta wersja onSaveInstanceState jest metodą z klasy RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Kluczem do poprawnego działania tego rozwiązania jest upewnienie się, że wywołujesz onRestoreInstanceState po podłączeniu adaptera, jak niektórzy wskazywali w innych wątkach. Jednak rzeczywiste wywołanie metody jest znacznie prostsze niż wiele osób wskazywało.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
I Ustaw zmienne w onCreate (), zapisz pozycję przewijania w onPause () i ustaw pozycję przewijania w 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);
}
}
Nie musisz już samodzielnie zapisywać i przywracać stanu. Jeśli ustawisz unikalny identyfikator w xml i recyklerView.setSaveEnabled (true) (domyślnie true) system automatycznie to zrobi. Tutaj jest więcej na ten temat: http://trickyandroid.com/saving-android-view-state-correctly/
Wszyscy menedżerowie układów w bibliotece wsparcia już wiedzą, jak zapisywać i przywracać pozycję przewijania.
Pozycja przewijania jest przywracana przy pierwszym przebiegu układu, co ma miejsce, gdy spełnione są następujące warunki:
RecyclerView
RecyclerView
Zazwyczaj menedżer układu ustawiasz w XML, więc wszystko, co musisz teraz zrobić, to
RecyclerView
Możesz to zrobić w dowolnym momencie, nie jest to ograniczone do Fragment.onCreateView
aniActivity.onCreate
.
Przykład: upewnij się, że adapter jest podłączony za każdym razem, gdy dane są aktualizowane.
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
}
}
Zapisana pozycja przewijania jest obliczana na podstawie następujących danych:
Dlatego możesz spodziewać się niewielkich niedopasowań, np. Jeśli Twoje przedmioty mają różne wymiary w orientacji pionowej i poziomej.
RecyclerView
/ ScrollView
/ Cokolwiek musi mieć android:id
na stanie mają zostać zapisane.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Oto moje Podejście do ich uratowania i utrzymania stanu recyklingu w fragmentach. Najpierw dodaj zmiany konfiguracji w swojej aktywności rodzica, jak poniżej.
android:configChanges="orientation|screenSize"
Następnie w swoim fragmencie zadeklaruj te metody wystąpienia
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Dodaj poniższy kod w swojej metodzie @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);
}
Dodaj metodę onConfigartionChanged, jak poniżej.
@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);
}
To rozwiązanie rozwiąże Twój problem. jeśli masz innego menedżera układu, po prostu zastąp górnego menedżera układu.
W ten sposób przywracam pozycję RecyclerView za pomocą GridLayoutManager po obróceniu, gdy trzeba ponownie załadować dane z Internetu za pomocą AsyncTaskLoader.
Utwórz zmienną globalną Parcelable i GridLayoutManager oraz statyczny ciąg końcowy:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Zapisz stan gridLayoutManager w onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Przywróć w 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);
}
}
Następnie, gdy program ładujący pobiera dane z Internetu, przywracasz pozycję widoku recyklingu w onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. oczywiście musisz utworzyć wystąpienie gridLayoutManager w onCreate. Twoje zdrowie
Miałem ten sam wymóg, ale przedstawione tutaj rozwiązania nie do końca pozwoliły mi przejść przez linię, ze względu na źródło danych dla recyclinglerView.
Wyodrębniałem stan LinearLayoutManager RecyclerViews w onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Przesyłka state
jest zapisywana onSaveInstanceState(Bundle outState)
i ponownie wypakowywana onCreate(Bundle savedInstanceState)
, kiedysavedInstanceState != null
.
Jednak adapter recyklerView jest wypełniany i aktualizowany przez obiekt ViewModel LiveData zwracany przez wywołanie DAO do bazy danych 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);
}
});
W końcu stwierdziłem, że przywrócenie stanu instancji bezpośrednio po ustawieniu danych w adapterze zachowuje stan przewijania między obrotami.
Podejrzewam, że to też jest spowodowane tym LinearLayoutManager
że stan, który przywróciłem, był nadpisywany, gdy dane zostały zwrócone przez wywołanie bazy danych, lub stan przywrócenia był bez znaczenia dla „pustego” LinearLayoutManager.
Jeśli dane adaptera są dostępne bezpośrednio (tj. Nie są zależne od wywołania bazy danych), wówczas przywrócenie stanu instancji w LinearLayoutManager można wykonać po ustawieniu adaptera w recyklinguView.
Różnica między tymi dwoma scenariuszami trzymała mnie w napięciu na wieki.
Android API Level 28, ja po prostu upewnić się, że ja skonfigurować LinearLayoutManager
i RecyclerView.Adapter
moim Fragment#onCreateView
sposobem, a wszystko po prostu Pracował ™ ️. Nie musiałem nic robić onSaveInstanceState
ani onRestoreInstanceState
pracować.
Odpowiedź Eugena Pechaneca wyjaśnia, dlaczego to działa.
Począwszy od wersji 1.2.0-alpha02 biblioteki androidx reclerView, jest teraz zarządzana automatycznie. Po prostu dodaj za pomocą:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
I użyć:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
Wyliczenie StateRestorationPolicy ma 3 opcje:
Zauważ, że w czasie tej odpowiedzi biblioteka recyclinglerView jest nadal w wersji alpha03, ale faza alfa nie jest odpowiednia do celów produkcyjnych .
Dla mnie problem polegał na tym, że za każdym razem, gdy zmieniałem adapter, konfigurowałem nowy layoutmanager, tracąc pozycję przewijania kolejki w programie recyclinglerView.
Chciałbym tylko podzielić się ostatnimi kłopotami, które napotykam z RecyclerView. Mam nadzieję, że skorzysta każdy, kto ma ten sam problem.
Wymagania mojego projektu: Mam więc widok RecyclerView, który zawiera listę niektórych elementów, które można kliknąć, w moim głównym działaniu (działanie-A). Po kliknięciu elementu zostanie wyświetlone nowe działanie ze szczegółami elementu (działanie-B).
Wdrożyłem w Manifeście pliku , że Activity-A jest rodzicem Activity-B, w ten sposób mam przycisk Wstecz lub Home na ActionBar Activity-B
Problem: Za każdym razem, gdy nacisnąłem przycisk Wstecz lub Strona główna na pasku akcji Activity-B, Activity-A przechodzi do pełnego cyklu życia działania, zaczynając od onCreate ()
Mimo że zaimplementowałem onSaveInstanceState () zapisując listę adaptera RecyclerView, gdy Activity-A rozpoczyna swój cykl życia, saveInstanceState jest zawsze null w metodzie onCreate ().
Dalsze kopanie w Internecie napotkałem ten sam problem, ale osoba zauważyła, że przycisk Wstecz lub Strona główna pod urządzeniem Anroid (domyślny przycisk Wstecz / Strona główna) Aktywność-A nie przechodzi do cyklu życia aktywności.
Rozwiązanie:
Włączyłem przycisk Home lub Back w Activity-B
W metodzie onCreate () dodaj tę linię supportActionBar? .SetDisplayHomeAsUpEnabled (true)
W metodzie overide fun onOptionsItemSelected () sprawdziłem item.ItemId, na który element został kliknięty na podstawie id. Identyfikator przycisku Wstecz to
android.R.id.home
Następnie zaimplementuj wywołanie funkcji finish () w android.R.id.home
To zakończy działanie B i przyniesie Acitivy-A bez przechodzenia przez cały cykl życia.
Jak na moje wymagania jest to dotychczas najlepsze rozwiązanie.
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)
}
Miałem ten sam problem z niewielką różnicą, korzystałem z Databinding i ustawiałem adaptera recyclinglerView i layoutManager w metodach wiązania.
Problem polegał na tym, że wiązanie danych miało miejsce po onResume, więc resetował mój layoutManager po onResume, a to oznacza, że za każdym razem przewijał się z powrotem do pierwotnego miejsca. Więc kiedy przestałem ustawiać layoutManager w metodach adaptera Databinding, znowu działa dobrze.
W moim przypadku ustawiałem RecyclerView layoutManager
zarówno w XML, jak iw onViewCreated
. Usunięcie zadania onViewCreated
naprawiło to.
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();
}
Dlaczego używam OnSaveInstanceState()
w onPause()
oznacza, że przełączenie do innej czynności zostanie wywołane onPause, zapisze tę pozycję przewijania i przywróci pozycję, gdy wrócimy z innej czynności.