¿Cómo guardar la posición de desplazamiento de RecyclerView usando RecyclerView.State?


Respuestas:


37

¿Cómo planeas guardar la última posición guardada con RecyclerView.State?

Siempre puede confiar en el buen estado de guardado. Extienda RecyclerViewy 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];
            }
        };
    }

1
ahora tengo otra pregunta, ¿cómo, no, qué puede hacer RecyclerView.State?
陈文涛

4
esto realmente funciona? He intentado esto, pero sigo recibiendo una excepción de tiempo de ejecución como esta: MyRecyclerView $ SavedState no se puede transmitir a android.support.v7.widget.RecyclerView $ SavedState
Daivid

2
En el estado de la instancia de restauración, necesitamos pasar el superState a su superclase (la vista del reciclador) de esta manera: super.onRestoreInstanceState (((ScrollPositionSavingRecyclerView.SavedState) state) .getSuperState ());
micnoy

5
Esto no funciona si amplía RecyclerView, obtendrá BadParcelException.
EpicPandaForce

1
Nada de esto es necesario: si usa algo como LinearLayoutManager / GridLayoutManager, la "posición de desplazamiento" ya se guarda automáticamente para usted. (Es mAnchorPosition en LinearLayoutManager.SavedState). Si no está viendo eso, entonces algo anda mal con la forma en que está volviendo a aplicar los datos.
Brian Yencho

229

Actualizar

A partir del recyclerview:1.2.0-alpha02lanzamiento StateRestorationPolicyse 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

Respuesta antigua

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

21
Esta es claramente la mejor respuesta, ¿por qué no se acepta? Tenga en cuenta que cualquier LayoutManager tiene eso, no solo LinearLayoutManager, también GridLayoutManager, y si no es una implementación falsa.
Paul Woitaschek

linearLayoutManager.onInstanceState () siempre devuelve nulo -> verifique el código fuente. Desafortunadamente no funciona.
luckyhandler

1
¿No guarda RecyclerView el estado de su LayoutManager en su propio onSaveInstanceState? Mirando v24 de la
biblioteca de

3
Esto funciona en uno solo, RecyclerViewpero no si tiene un ViewPagercon un RecycerViewen cada página.
Kris B

1
@Ivan, creo que funciona en un fragmento (recién verificado), pero no en onCreateView, sino después de la carga de datos. Vea la respuesta de @Farmaker aquí.
CoolMind

81

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()


Esto funciona, pero puede que tenga que reiniciar el lastFirstVisiblePositionque 0despué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.
blueware

¡Excelente! Satisface tanto volver de una actividad detallada a la lista como guardar el estado en la rotación de pantalla
Javi

¿Qué hacer si mi RecyclerView está en SwipeRefreshLayout?
Morozov

2
¿Por qué no funcionaría la primera opción de restauración, pero sí la segunda?
lucidbrot

1
@MehranJalili parece ser unRecyclerView
Vlad

22

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

1
Ésa es la solución precisa. : D
Afjalur Rahman Rana

1
para mí, onSaveInstanceState no se llama, por lo que guardar el estado en onPause parece una mejor opción. También estoy haciendo estallar el backstack para volver al fragmento.
filthy_wizard

para la carga en estado. lo que hago en onViewCreated. ¿Solo parece funcionar cuando estoy en modo de depuración y uso puntos de interrupción? o si agrego un retraso y pongo la ejecución en una corrutina.
filthy_wizard

21

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

Mantenga el desplazamiento del artículo también. Agradable. ¡Gracias!
t0m


14

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:

  • un administrador de diseño se adjunta al RecyclerView
  • se adjunta un adaptador al RecyclerView

Normalmente, configura el administrador de diseño en XML, por lo que todo lo que tiene que hacer ahora es

  1. Cargue el adaptador con datos
  2. Luego conecte el adaptador alRecyclerView

Puede hacer esto en cualquier momento, no está limitado a Fragment.onCreateViewni 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:

  • Posición del adaptador del primer elemento en pantalla
  • Desplazamiento de píxeles de la parte superior del elemento desde la parte superior de la lista (para diseño vertical)

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:idestado para que se guarde.


Esto fue todo para mí. Estaba configurando el adaptador inicialmente y luego actualizando sus datos cuando estaban disponibles. Como se explicó anteriormente, dejarlo hasta que los datos estén disponibles hace que se desplace automáticamente hacia donde estaba.
NeilS

Gracias @Eugen. ¿Existe alguna documentación sobre cómo guardar y restaurar la posición de desplazamiento de LayoutManager?
Adam Hurwitz

@AdamHurwitz ¿Qué tipo de documentación esperaría? Lo que describí es un detalle de implementación de LinearLayoutManager, así que lea su código fuente.
Eugen Pechanec

1
Aquí hay un enlace a LinearLayoutManager.SavedState: cs.android.com/androidx/platform/frameworks/support/+/…
Eugen Pechanec

Gracias, @EugenPechanec. Esto funcionó para mí para las rotaciones de dispositivos. También estoy usando una vista de navegación inferior y cuando cambio a otra pestaña inferior y regreso, la lista comienza nuevamente desde el principio. ¿Alguna idea de por qué es esto?
schv09

4

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.


Es posible que no necesite un controlador para conservar los datos. Los métodos del ciclo de vida serán suficientes para guardar / cargar.
Mahi

@sayaMahi ¿Puedes describirlo de una manera adecuada? También he visto que onConfigurationChanged evita llamar al método de destrucción. por lo que no es una solución adecuada.
Pawan kumar sharma

3

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


3

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 statese 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 LinearLayoutManagerestado 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.


3

En el nivel 28 de la API de Android, simplemente me aseguro de configurar mi método LinearLayoutManagery RecyclerView.Adapteren mi Fragment#onCreateView, y todo Just Worked ™ ️. No necesitaba hacer nada onSaveInstanceStateni onRestoreInstanceStatetrabajar.

La respuesta de Eugen Pechanec explica por qué funciona.


2

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:

  • PERMITIR: el estado predeterminado, que restaura el estado de RecyclerView inmediatamente, en la siguiente pasada de diseño
  • PREVENT_WHEN_EMPTY: restaura el estado de RecyclerView solo cuando el adaptador no está vacío (adapter.getItemCount ()> 0). Si sus datos se cargan de forma asincrónica, RecyclerView espera hasta que se cargan los datos y solo entonces se restaura el estado. Si tiene elementos predeterminados, como encabezados o indicadores de progreso de carga como parte de su Adaptador, entonces debe usar la opción PREVENT , a menos que los elementos predeterminados se agreguen usando MergeAdapter . MergeAdapter espera a que todos sus adaptadores estén listos y solo entonces restaura el estado.
  • PREVENT: toda la restauración del estado se aplaza hasta que establezca ALLOW o PREVENT_WHEN_EMPTY.

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 .


Se ahorró tanto exceso de trabajo. De lo contrario, tuvimos que guardar el índice de clics de la lista, restaurar elementos, evitar volver a dibujar en onCreateView, aplicar scrollToPosition (o hacer lo mismo con otras respuestas) y, sin embargo, el usuario pudo notar la diferencia después de regresar
Rahul Kahale

Además, si quiero guardar la posición de RecyclerView después de la navegación a través de la aplicación ... ¿hay alguna herramienta para ello?
StayCool

@RubenViguera ¿Por qué alpha no es para fines de producción? Quiero decir, ¿eso es realmente malo?
StayCool

1

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.


1

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:

  1. Eliminé la etiqueta para la actividad de los padres en el manifiesto de la Actividad-B
  2. Activé el botón de inicio o retroceso en la Actividad-B

    En el método onCreate () agregue esta línea supportActionBar? .SetDisplayHomeAsUpEnabled (true)

  3. 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)
}

1

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.


como lo lograste
Kedar Sukerkar

0

En mi caso, estaba configurando RecyclerView layoutManagertanto en XML como en onViewCreated. La eliminación de la asignación se onViewCreatedsolucionó.

with(_binding.list) {
//  layoutManager =  LinearLayoutManager(context)
    adapter = MyAdapter().apply {
        listViewModel.data.observe(viewLifecycleOwner,
            Observer {
                it?.let { setItems(it) }
            })
    }
}

-1

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.


2
El campo público estático no es bueno. Los datos globales y los singleton no son buenos.
Weston

@weston Lo entendí ahora. Cambié savestate a onpause para eliminar global.
Steve
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.