Tengo una pregunta sobre RecyclerView.State de Android .
Estoy usando un RecyclerView, ¿cómo podría usarlo y vincularlo con RecyclerView.State?
Mi propósito es guardar la posición de desplazamiento de RecyclerView .
Tengo una pregunta sobre RecyclerView.State de Android .
Estoy usando un RecyclerView, ¿cómo podría usarlo y vincularlo con RecyclerView.State?
Mi propósito es guardar la posición de desplazamiento de RecyclerView .
Respuestas:
¿Cómo planeas guardar la última posición guardada con RecyclerView.State
?
Siempre puede confiar en el buen estado de guardado. Extienda RecyclerView
y anule onSaveInstanceState () y 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 partir del recyclerview:1.2.0-alpha02
lanzamiento StateRestorationPolicy
se ha introducido. Podría ser un mejor enfoque para el problema dado.
Este tema se ha tratado en el artículo medio de desarrolladores de Android .
Además, @ rubén-viguera compartió más detalles en la respuesta a continuación. https://stackoverflow.com/a/61609823/892500
Si está utilizando LinearLayoutManager, viene con la api linearLayoutManagerInstance.onSaveInstanceState () preconstruida y la restauración api linearLayoutManagerInstance.onRestoreInstanceState (...)
Con eso, puede guardar el parcelable devuelto en su outState. p.ej,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
y restaure la posición de restauración con el estado que guardó. p.ej,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Para resumir todo, su código final se verá algo así como
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());
}
Editar: también puede usar las mismas apis con GridLayoutManager, ya que es una subclase de LinearLayoutManager. Gracias @wegsehen por la sugerencia.
Editar: Recuerde, si también está cargando datos en un hilo en segundo plano, deberá llamar a onRestoreInstanceState dentro de su método onPostExecute / onLoadFinished para que la posición se restaure al cambiar de orientación, por ejemplo
@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
pero no si tiene un ViewPager
con un RecycerView
en cada página.
onCreateView
, sino después de la carga de datos. Vea la respuesta de @Farmaker aquí.
Tienda
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Restaurar
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
y si eso no funciona, intenta
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Guarde onPause()
y restaureonResume()
lastFirstVisiblePosition
que 0
después de restaurarlo. Este caso es útil cuando tiene un fragmento / actividad que carga diferentes datos en uno RecyclerView
, por ejemplo, una aplicación de exploración de archivos.
SwipeRefreshLayout
?
RecyclerView
Quería guardar la posición de desplazamiento de Recycler View al navegar fuera de la actividad de mi lista y luego hacer clic en el botón Atrás para navegar hacia atrás. Muchas de las soluciones proporcionadas para este problema eran mucho más complicadas de lo necesario o no funcionaban para mi configuración, así que pensé en compartir mi solución.
Primero guarde el estado de su instancia en onPause como muchos lo han mostrado. Creo que vale la pena enfatizar aquí que esta versión de onSaveInstanceState es un método de la clase RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
La clave para que esto funcione correctamente es asegurarse de llamar a onRestoreInstanceState después de conectar su adaptador, como algunos han indicado en otros hilos. Sin embargo, la llamada al método real es mucho más simple de lo que muchos han indicado.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
Establezco variables en onCreate (), guardo la posición de desplazamiento en onPause () y establezco la posición de desplazamiento en 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);
}
}
Ya no tiene que guardar y restaurar el estado usted mismo. Si establece una ID única en xml y reciclaView.setSaveEnabled (verdadero) (verdadero por defecto), el sistema lo hará automáticamente. Aquí hay más sobre esto: http://trickyandroid.com/saving-android-view-state-correctly/
Todos los administradores de diseño incluidos en la biblioteca de soporte ya saben cómo guardar y restaurar la posición de desplazamiento.
La posición de desplazamiento se restaura en la primera pasada de diseño, lo que ocurre cuando se cumplen las siguientes condiciones:
RecyclerView
RecyclerView
Normalmente, configura el administrador de diseño en XML, por lo que todo lo que tiene que hacer ahora es
RecyclerView
Puede hacer esto en cualquier momento, no está limitado a Fragment.onCreateView
ni Activity.onCreate
.
Ejemplo: asegúrese de que el adaptador esté conectado cada vez que se actualicen los datos.
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 posición de desplazamiento guardada se calcula a partir de los siguientes datos:
Por lo tanto, puede esperar un ligero desajuste, por ejemplo, si sus artículos tienen diferentes dimensiones en orientación vertical y horizontal.
El RecyclerView
/ ScrollView
/ lo que necesita tener un android:id
estado para que se guarde.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Aquí está mi enfoque para guardarlos y mantener el estado de la vista del reciclaje en un fragmento. Primero, agregue cambios de configuración en su actividad principal como se muestra a continuación.
android:configChanges="orientation|screenSize"
Luego, en su Fragmento, declare estos métodos de instancia
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Agregue el siguiente código en su método @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);
}
Agregue el método onConfigartionChanged como se muestra a continuación.
@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);
}
Este enfoque solucionará su problema. Si tiene un administrador de diseño diferente, simplemente reemplace el administrador de diseño superior.
Así es como restauro la posición de RecyclerView con GridLayoutManager después de la rotación cuando necesita volver a cargar datos de Internet con AsyncTaskLoader.
Cree una variable global de Parcelable y GridLayoutManager y una cadena final estática:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Guardar estado de gridLayoutManager en onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Restaurar en 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);
}
}
Luego, cuando el cargador obtiene datos de Internet, restaura la posición de la vista del reciclador en onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
..por supuesto, debe crear una instancia de gridLayoutManager dentro de onCreate. Salud
Tuve el mismo requisito, pero las soluciones aquí no me ayudaron a cruzar la línea, debido a la fuente de datos para recyclingView.
Estaba extrayendo el estado LinearLayoutManager de RecyclerViews en onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
se guarda onSaveInstanceState(Bundle outState)
y se vuelve a extraer en onCreate(Bundle savedInstanceState)
, cuando savedInstanceState != null
.
Sin embargo, el adaptador de recyclerView se llena y actualiza mediante un objeto ViewModel LiveData devuelto por una llamada DAO a una base de datos de 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);
}
});
Finalmente descubrí que restaurar el estado de la instancia directamente después de que los datos se configuran en el adaptador mantendría el estado de desplazamiento en las rotaciones.
Sospecho que esto se debe a que el LinearLayoutManager
estado que había restaurado se estaba sobrescribiendo cuando los datos fueron devueltos por la llamada a la base de datos, o el estado restaurado no tenía sentido contra un LinearLayoutManager 'vacío'.
Si los datos del adaptador están disponibles directamente (es decir, no son contigentes en una llamada a la base de datos), entonces se puede restaurar el estado de la instancia en LinearLayoutManager después de que el adaptador se haya configurado en recyclingView.
La distinción entre los dos escenarios me detuvo durante años.
En el nivel 28 de la API de Android, simplemente me aseguro de configurar mi método LinearLayoutManager
y RecyclerView.Adapter
en mi Fragment#onCreateView
, y todo Just Worked ™ ️. No necesitaba hacer nada onSaveInstanceState
ni onRestoreInstanceState
trabajar.
La respuesta de Eugen Pechanec explica por qué funciona.
A partir de la versión 1.2.0-alpha02 de la biblioteca Androidx RecyclerView, ahora se administra automáticamente. Solo agrégalo con:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
Y use:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
La enumeración StateRestorationPolicy tiene 3 opciones:
Tenga en cuenta que en el momento de esta respuesta, la biblioteca RecyclerView todavía está en alpha03, pero la fase alfa no es adecuada para fines de producción .
Para mí, el problema fue que configuré un nuevo administrador de diseño cada vez que cambié mi adaptador, perdiendo la posición de desplazamiento de la cola en la vista de reciclaje.
Solo me gustaría compartir la situación reciente que encuentro con RecyclerView. Espero que cualquier persona que experimente el mismo problema se beneficie.
Requisito de mi proyecto: Tengo un RecyclerView que enumera algunos elementos en los que se puede hacer clic en mi Actividad principal (Actividad-A). Cuando se hace clic en el elemento, se muestra una nueva actividad con los detalles del elemento (Actividad-B).
Implementé en el archivo de manifiesto que la Actividad-A es el padre de la Actividad-B, de esa manera, tengo un botón de retroceso o inicio en la barra de acciones de la Actividad-B
Problema: cada vez que presioné el botón Atrás o Inicio en la barra de acción de Actividad-B, la Actividad-A entra en el ciclo de vida de actividad completo comenzando desde onCreate ()
Aunque implementé un onSaveInstanceState () guardando la Lista del Adaptador de RecyclerView, cuando Activity-A inicia su ciclo de vida, saveInstanceState siempre es nulo en el método onCreate ().
Investigando más en Internet, encontré el mismo problema, pero la persona notó que el botón Atrás o Inicio debajo del dispositivo Anroid (botón Atrás / Inicio predeterminado), la Actividad-A no entra en el Ciclo de vida de la actividad.
Solución:
Activé el botón de inicio o retroceso en la Actividad-B
En el método onCreate () agregue esta línea supportActionBar? .SetDisplayHomeAsUpEnabled (true)
En el método overide fun onOptionsItemSelected (), verifiqué el item.ItemId en el que se hizo clic en el elemento en función de la identificación. La identificación del botón de retroceso es
android.R.id.home
Luego implemente una llamada a la función finish () dentro de android.R.id.home
Esto finalizará la Actividad-B y traerá la Actividad-A sin pasar por todo el ciclo de vida.
Para mi requerimiento, esta es la mejor solución hasta ahora.
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)
}
Tuve el mismo problema con una diferencia menor, estaba usando Databinding y estaba configurando el adaptador RecyclerView y el layoutManager en los métodos de enlace.
El problema era que el enlace de datos estaba sucediendo después de onResume, por lo que estaba restableciendo mi layoutManager después de onResume y eso significa que se estaba desplazando al lugar original cada vez. Entonces, cuando dejé de configurar layoutManager en los métodos del adaptador de enlace de datos, funciona bien nuevamente.
En mi caso, estaba configurando RecyclerView layoutManager
tanto en XML como en onViewCreated
. La eliminación de la asignación se onViewCreated
solucionó.
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();
}
Por qué estoy usando OnSaveInstanceState()
in onPause()
significa, mientras que el cambio a otra actividad onPause se llamará. Guardará esa posición de desplazamiento y restaurará la posición cuando regresemos de otra actividad.