Desplace RecyclerView para mostrar el elemento seleccionado en la parte superior


215

Estoy buscando una manera de desplazarme RecyclerViewpara mostrar el elemento seleccionado en la parte superior.

En una ListView, pude hacer eso usando scrollTo(x,y)y obteniendo la parte superior del elemento que necesita ser centrado.

Algo como:

@Override
public void onItemClick(View v, int pos){
    mylistView.scrollTo(0, v.getTop());
}

El problema es que RecyclerViewdevuelve un error al usar su scrollTométodo diciendo

RecyclerView no admite el desplazamiento a una posición absoluta

¿Cómo puedo desplazar a RecyclerViewpara colocar el elemento seleccionado en la parte superior de la vista?

Respuestas:


405

Si está utilizando el LinearLayoutManagero escalonado GridLayoutManager, cada uno tiene un método scrollToPositionWithOffset que toma la posición y también el desplazamiento del inicio del elemento desde el inicio del RecyclerView, lo que parece que lograría lo que necesita (estableciendo el desplazamiento en 0 debe alinearse con la parte superior).

Por ejemplo:

//Scroll item 2 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(2, 20);

34
Si alguien necesita esto para restaurar la posición de desplazamiento de un RecyclerView, así es como se guardan las posiciones de desplazamiento (los dos argumentos para el método scrollToPositionWithOffset): int index = linearLayoutManager.findFirstVisibleItemPosition (); Ver v = linearLayoutManager.getChildAt (0); int top = (v == nulo)? 0: (v.getTop () - linearLayoutManager.getPaddingTop ());
DominicM

Lo que parece que no entiendo acerca de esto es cuándo llamar a scrollToPosition. si el oyente de selección está en las vistas individuales, ¿cómo se notificará al objeto RecyclerView (que tiene acceso a su administrador de diseño que puede llamar a scrolToPosition) que se ha seleccionado un elemento?
Nonos

@Nonos, primero puede almacenar el LayoutManager que usa en RecylerView.setLayoutManager () y luego acceder a él directamente. De esta manera: LinearLayoutManager llm = new LinearLayoutManager (this); mRecyclerView.setLayoutManager (llm); ... (más adelante en el código) ... llm.scrollToPositionWithOffset (2, 20);
Niraj

19
¿Qué sucede si quiero desplazarme hacia la parte superior "sin problemas"?
Tiago

@Tiago, no he jugado con eso, pero esta serie de artículos de 3 partes puede ayudar: blog.stylingandroid.com/scrolling-recyclerview-part-1
Niraj

93

Si está buscando el LinearLayout Manager vertical, puede lograr un desplazamiento suave usando una costumbre LinearSmoothScroller:

import android.content.Context;
import android.graphics.PointF;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;

public class SnappingLinearLayoutManager extends LinearLayoutManager {

    public SnappingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                       int position) {
        RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private class TopSnappedSmoothScroller extends LinearSmoothScroller {
        public TopSnappedSmoothScroller(Context context) {
            super(context);

        }

        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return SnappingLinearLayoutManager.this
                    .computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int getVerticalSnapPreference() {
            return SNAP_TO_START;
        }
    }
}

use una instancia del administrador de diseño en la vista de reciclaje y luego la llamada recyclerView.smoothScrollToPosition(pos);se desplazará suavemente a la posición seleccionada en la parte superior de la vista de reciclador


1
Respuesta realmente útil! También getHorizontalSnapPreference()anulo en TopSnappedSmoothScroller para listas horizontales.
Juan Saravia

66
¿Estoy en lo cierto al observar que esto solo funciona para elementos que todavía tienen suficiente después / debajo de ellos para llenar el resto RecyclerView? Es decir: esto no parece funcionar para el último elemento, independientemente de SNAP_TO_STARTlos RecyclerViewúnicos desplazamientos hasta que el elemento sea visible en la parte inferior pero no lo mueva completamente hacia arriba. ¿Alguna idea de cómo lograr esto? (He trabajado alrededor de esto al configurar un relleno personalizado en el RecyclerViewpero eso no parece realmente correcto ...)
david.mihola


a veces el elemento no se enfoca, tengo un foco que se puede dibujar para ver el elemento.
YLS

1
@ david.mihola ¿Has encontrado el camino 'correcto'? He configurado un relleno personalizado en mi vista de reciclador, pero estoy teniendo algunos problemas extraños ...
bernardo.g


28

Solo necesitas llamar recyclerview.scrollToPosition(position). ¡Esta bien!

Si desea llamarlo en el adaptador, simplemente deje que su adaptador tenga la instancia de recyclerview o la actividad o fragmento que contiene recyclerview, que implementa el método getRecyclerview()en ellos.

Espero que te pueda ayudar.


2
Esto funcionó para mí: recyclerView.scrollToPosition (0); ponme en la cima.
Ray Hunter

2
que no funciona tienes que usar linearLayoutManager.scrollToPositionWithOffset (pos, 0);
Anil Ugale

@Anil Ugale Eso no tiene sentido. Por supuesto que funciona. Puede usar cualquier LayoutManager para un RecyclerView. ¿Por qué debería preocuparte más tarde cuando quieres desplazarte? Creo que estabas haciendo algo mal. Recyclerview.scrollToPosition (posición) es un método conveniente que llama a LayoutManager.scrollToPosition (posición).
El increíble Jan

1
@ TheincredibleJan no funciona en todos los casos, el layoutManager es generalmente responsable del desplazamiento de todos modos solo para su información.
Mohammed

11

lo mismo con el regulador de velocidad

public class SmoothScrollLinearLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 110f;
private Context mContext;

public SmoothScrollLinearLayoutManager(Context context,int orientation, boolean reverseLayout) {
    super(context,orientation,reverseLayout);
    mContext = context;
}

@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                   int position) {
    RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()){
        //This controls the direction in which smoothScroll looks for your view
        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return new PointF(0, 1);
        }

        //This returns the milliseconds it takes to scroll one pixel.
        @Override
        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
        }
    };
    smoothScroller.setTargetPosition(position);
    startSmoothScroll(smoothScroller);
}


private class TopSnappedSmoothScroller extends LinearSmoothScroller {
    public TopSnappedSmoothScroller(Context context) {
        super(context);

    }

    @Override
    public PointF computeScrollVectorForPosition(int targetPosition) {
        return SmoothScrollLinearLayoutManager.this
                .computeScrollVectorForPosition(targetPosition);
    }

    @Override
    protected int getVerticalSnapPreference() {
        return SNAP_TO_START;
    }
}
}

8

Prueba lo que funcionó para mí genial!

Crea una variable private static int displayedposition = 0;

Ahora para la posición de su RecyclerView en su actividad.

myRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                LinearLayoutManager llm = (LinearLayoutManager) myRecyclerView.getLayoutManager();


                displayedposition = llm.findFirstVisibleItemPosition();


            }
        });

Coloque esta declaración donde desee que coloque el sitio anterior que se muestra en su vista.

LinearLayoutManager llm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
        llm.scrollToPositionWithOffset(displayedposition , youList.size());

Bueno, eso es todo, funcionó bien para mí


7

simplemente llame a este método simplemente:

((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(yourItemPosition,0);

en vez de:

recyclerView.scrollToPosition(yourItemPosition);

"en vez de"? Distwo no mencionó "scrollToPosition". Si lo hubiera usado, no habría habido ningún problema. scrollToPosition () según lo previsto.
El increíble Jan

1
Por extraño que parezca, usar scrollToPositionWithOffset funciona para mí y recyclerview.scrollToPosition no lo hizo. Sin embargo, le pediría a Amir que cambie RecyclerView a recyclerview, porque da la sensación de que los métodos son estáticos y no lo son.
Mohit

6

Lo que hice para restaurar la posición de desplazamiento después de actualizar RecyclerView al hacer clic en el botón:

if (linearLayoutManager != null) {

    index = linearLayoutManager.findFirstVisibleItemPosition();
    View v = linearLayoutManager.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
    Log.d("TAG", "visible position " + " " + index);
}

else{
    index = 0;
}

linearLayoutManager = new LinearLayoutManager(getApplicationContext());
linearLayoutManager.scrollToPositionWithOffset(index, top);

obtener el desplazamiento del primer elemento visible desde la parte superior antes de crear el objeto linearLayoutManager y después de instanciarlo, se llamó al scrollToPositionWithOffset del objeto LinearLayoutManager.


6

No sé por qué no encontré la mejor respuesta, pero es realmente simple.

recyclerView.smoothScrollToPosition(position);

Sin errores

Crea animaciones


¿Dónde pondrás esto en actividad o adaptador?
Arnold Brown

3

desplazarse en una posición particular
y esto me ayudó mucho. por clic oyente puede obtener la posición en su adaptador

layoutmanager.scrollToPosition(int position);

3
If you want to scroll automatic without show scroll motion then you need to write following code:

**=> mRecyclerView.getLayoutManager().scrollToPosition(position);**

If you want to display scroll motion then you need to add following code.
=>Step 1: You need to declare SmoothScroller.

RecyclerView.SmoothScroller smoothScroller = new
                LinearSmoothScroller(this.getApplicationContext()) {
                    @Override
                    protected int getVerticalSnapPreference() {
                        return LinearSmoothScroller.SNAP_TO_START;
                    }
                };

=>step 2: You need to add this code any event you want to perform scroll to specific position.
=>First you need to set target position to SmoothScroller.

**smoothScroller.setTargetPosition(position);**

=>Then you need to set SmoothScroller to LayoutManager.
                        **mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller);**

3

Ninguna de las respuestas explica cómo mostrar los últimos elementos en la parte superior. Por lo tanto, las respuestas funcionan solo para los elementos que todavía tienen suficientes elementos encima o debajo de ellos para completar el resto RecyclerView. Por ejemplo, si hay 59 elementos y se selecciona un 56º elemento, debe estar en la parte superior como en la imagen a continuación:

RecyclerView: el elemento 56 está seleccionado Podríamos manejar esos casos, utilizar linearLayoutManager.scrollToPositionWithOffset(pos, 0)y una lógica adicional en Adapterde RecyclerView- mediante la adición de un margen de encargo del último artículo (si el último elemento no es visible, entonces significa que hay suficiente espacio en el relleno RecyclerView). El margen personalizado podría ser una diferencia entre la altura de la vista raíz y la altura del elemento. Entonces, su Adapterfor RecyclerViewse vería de la siguiente manera:

...
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    ...

    int bottomHeight = 0;
    int itemHeight = holder.itemView.getMeasuredHeight();
    // if it's the last item then add a bottom margin that is enough to bring it to the top
    if (position == mDataSet.length - 1) {
        bottomHeight = Math.max(0, mRootView.getMeasuredHeight() - itemHeight);
    }
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
    params.setMargins(0, 0, params.rightMargin, bottomHeight);
    holder.itemView.setLayoutParams(params);

    ...
} 
...

2

En mi caso, RecyclerViewtengo un top acolchado como este

<android.support.v7.widget.RecyclerView
     ...
     android:paddingTop="100dp"
     android:clipToPadding="false"
/>

Luego, para desplazar un elemento hacia arriba, necesito

recyclerViewLinearLayoutManager.scrollToPositionWithOffset(position, -yourRecyclerView.getPaddingTop());

1

Si LayoutManageres tu LinearLayoutManagerlo usas scrollToPositionWithOffset(position,0);y hará que tu elemento sea el primer elemento visible de la lista. De lo contrario, puede usar smoothScrollToPositionenRecyclerView directamente.

Terminé usando el siguiente código.

 RecyclerView.LayoutManager layoutManager = mainList.getLayoutManager();
        if (layoutManager instanceof LinearLayoutManager) {
            // Scroll to item and make it the first visible item of the list.
            ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0);
        } else {
            mainList.smoothScrollToPosition(position);
        }

-2

Utilizo el siguiente código para desplazar suavemente un elemento (thisView) a la parte superior.
Funciona también para GridLayoutManager con vistas de diferentes alturas:

View firstView = mRecyclerView.getChildAt(0);
int toY = firstView.getTop();
int firstPosition = mRecyclerView.getChildAdapterPosition(firstView);
View thisView = mRecyclerView.getChildAt(thisPosition - firstPosition);
int fromY = thisView.getTop();

mRecyclerView.smoothScrollBy(0, fromY - toY);

Parece funcionar lo suficientemente bien como para una solución rápida.


1
¿Cuál es el valor de thisPosition?
pRaNaY

es la posición a la que desea desplazarse suavemente
Jan Rabe

¿Por qué no simplemente usar smoothScrollToPosition (int position) sin ningún cálculo? No importa desde donde comienzas, ¿verdad?
El increíble Jan
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.