La vista del reciclador dentro de NestedScrollView hace que el desplazamiento comience en el medio

12 minutos de lectura

avatar de usuario
Lucas Correa

Obtengo un comportamiento de desplazamiento extraño cuando agrego un RecyclerView dentro de un NestedScrollView.

Lo que sucede es que cada vez que la vista de desplazamiento tiene más filas de las que se pueden mostrar en la pantalla, tan pronto como se inicia la actividad, NestedScrollView comienza con un desplazamiento desde la parte superior (imagen 1). Si hay pocos elementos en la vista de desplazamiento para que se puedan mostrar todos a la vez, esto no sucede (imagen 2).

Estoy usando la versión 23.2.0 de la biblioteca de soporte.

Imagen 1: INCORRECTO – comienza con desplazamiento desde la parte superior

Imagen 1

Imagen 2: CORRECTO – pocos elementos en la vista del reciclador

Imagen 2

Estoy pegando debajo de mi código de diseño:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

            <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="16dp"
                android:orientation="vertical">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Title:"
                    style="@style/TextAppearance.AppCompat.Caption"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="@dimen/bodyPadding"
                    style="@style/TextAppearance.AppCompat.Body1"
                    android:text="Neque porro quisquam est qui dolorem ipsum"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Subtitle:"
                    style="@style/TextAppearance.AppCompat.Caption"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    style="@style/TextAppearance.AppCompat.Body1"
                    android:padding="@dimen/bodyPadding"
                    android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>

            </LinearLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:focusable="false"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

¿Me estoy perdiendo de algo? ¿Alguien tiene alguna idea de cómo solucionar este problema?

Actualización 1

Funciona correctamente si coloco el siguiente código al inicializar mi Actividad:

sv.post(new Runnable() {
        @Override
        public void run() {
            sv.scrollTo(0,0);
        }
});

Donde sv es una referencia a NestedScrollView, sin embargo, parece un gran truco.

Actualización 2

Según lo solicitado, aquí está mi código de adaptador:

public abstract class ArrayAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {

    private List<T> mObjects;

    public ArrayAdapter(final List<T> objects) {
        mObjects = objects;
    }

    /**
     * Adds the specified object at the end of the array.
     *
     * @param object The object to add at the end of the array.
     */
    public void add(final T object) {
        mObjects.add(object);
        notifyItemInserted(getItemCount() - 1);
    }

    /**
     * Remove all elements from the list.
     */
    public void clear() {
        final int size = getItemCount();
        mObjects.clear();
        notifyItemRangeRemoved(0, size);
    }

    @Override
    public int getItemCount() {
        return mObjects.size();
    }

    public T getItem(final int position) {
        return mObjects.get(position);
    }

    public long getItemId(final int position) {
        return position;
    }

    /**
     * Returns the position of the specified item in the array.
     *
     * @param item The item to retrieve the position of.
     * @return The position of the specified item.
     */
    public int getPosition(final T item) {
        return mObjects.indexOf(item);
    }

    /**
     * Inserts the specified object at the specified index in the array.
     *
     * @param object The object to insert into the array.
     * @param index  The index at which the object must be inserted.
     */
    public void insert(final T object, int index) {
        mObjects.add(index, object);
        notifyItemInserted(index);

    }

    /**
     * Removes the specified object from the array.
     *
     * @param object The object to remove.
     */
    public void remove(T object) {
        final int position = getPosition(object);
        mObjects.remove(object);
        notifyItemRemoved(position);
    }

    /**
     * Sorts the content of this adapter using the specified comparator.
     *
     * @param comparator The comparator used to sort the objects contained in this adapter.
     */
    public void sort(Comparator<? super T> comparator) {
        Collections.sort(mObjects, comparator);
        notifyItemRangeChanged(0, getItemCount());
    }
}

Y aquí está mi ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {
    private TextView txt;
    public ViewHolder(View itemView) {
        super(itemView);
        txt = (TextView) itemView;
    }

    public void render(String text) {
        txt.setText(text);
    }
}

Y aquí está el diseño de cada elemento en RecyclerView (es solo android.R.layout.simple_spinner_item – esta pantalla es solo para mostrar un ejemplo de este error):

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:textAlignment="inherit"/>

  • probado con android:focusableInTouchMode="false" por RecyclerView? smth claramente es “obligar a su diseño a ir al final … (¿dónde comienza cuando tiene 1295 elementos? abajo o solo un pequeño desplazamiento superior como la primera pantalla)

    – arrebatamiento

    30 de marzo de 2016 a las 16:44


  • Configure android:clipToPadding=“true” en su NestedScrollView .

    – natario

    30 de marzo de 2016 a las 16:45

  • también: mantener la vista desplazable dentro de otra vista desplazable de la misma manera no es un patrón muy bueno … espero que estés usando correctamente LayoutManager

    – arrebatamiento

    30 de marzo de 2016 a las 16:48

  • Intenté ambas sugerencias y, lamentablemente, ninguna funcionó. @snachmsm no importa la cantidad de elementos en la vista del reciclador, el desplazamiento siempre es el mismo. En cuanto a si colocar un RecyclerView dentro de un NestedScrollView es un buen patrón, en realidad lo recomendó un ingeniero de Google en plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T

    – Lucas Correa

    30 de marzo de 2016 a las 16:56

  • Incluso tengo el mismo problema cuando uso RecyclerView dentro de NestedScrollView. Es un mal patrón porque el patrón del reciclador en sí mismo no funcionará. Todas las vistas se dibujarán a la vez (ya que WRAP_CONTENT necesita la altura de la vista del reciclador). No habrá ningún reciclaje de vistas en segundo plano, por lo que el objetivo principal de la vista del reciclador en sí no es funcionar. Pero es fácil administrar los datos y dibujar el diseño usando la vista de reciclador, esa es la única razón por la que puede usar este patrón. Así que mejor no usarlo hasta que no lo necesites con seguridad.

    –Ashok Varma

    28 de abril de 2016 a las 6:04


avatar de usuario
Dmitri Gavrilko

Resolví tal problema configurando:

<ImageView ...
android:focusableInTouchMode="true"/>

a mi vista sobre RecyclerView (que se ocultó después de un desplazamiento no deseado). Intente establecer esta propiedad en su LinearLayout arriba de RecyclerView o en LinearLayout, que es un contenedor de RecyclerView (me ayudó en otro caso).

Como veo en la fuente de NestedScrollView, intenta enfocar al primer niño posible en onRequestFocusInDescendants y si solo se puede enfocar RecyclerView, gana.

Edite (gracias a Waran): y para un desplazamiento suave, no olvide configurar yourRecyclerView.setNestedScrollingEnabled(false);

  • Por cierto, debe agregar yourRecyclerView.setNestedScrollingEnabled(false); para que el desplazamiento sea suave

    – Waran-

    2 de junio de 2016 a las 14:19

  • @Kenji solo para la primera vista dentro de LinearLayout donde se coloca RecyclerView. O a ese LinearLayout (contenedor RecyclerView) en sí mismo si el primer cambio no ayuda.

    – Dmitri Gavrilko

    21 de diciembre de 2016 a las 18:02

  • @DmitryGavrilko Tengo un problema, donde RecycleView está dentro de NestedScrollview. No puedo usar recycleview.scrollToPosition(X); , simplemente no funciona. Intenté todo en los últimos 6 días, pero puedo superarlo. ¿cualquier sugerencia? Le estaría muy agradecido !

    – narancs

    6 de enero de 2017 a las 17:57

  • También puede configurar android:focusableInTouchMode="false"a recyclerView para que no necesite establecer verdadero para todas las demás vistas.

    – Aksiom

    06/01/2017 a las 21:32

  • De acuerdo con Dmitry Gavrilko, funcionó después de configurar android:focusableInTouchMode=”true” en el diseño lineal que contiene recyclerview. @Aksiom configurar android:focusableInTouchMode=”false” en recyclerView no funcionó para mí.

    – Shendre Kiran

    14 de julio de 2019 a las 7:26

avatar de usuario
jimit patel

En tus LinearLayout inmediatamente después NestedScrollViewusar android:descendantFocusability de la siguiente manera

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp"
        android:descendantFocusability="blocksDescendants">

EDITAR

Dado que muchos de ellos obtienen esta respuesta útil, también se proporcionará una explicación.

El uso de descendantFocusability es dado aquí. y a partir de focusableInTouchMode sobre aquí. entonces usando blocksDescendants en descendantFocusability no permita que el niño se concentre mientras lo toca y, por lo tanto, se puede detener el comportamiento no planificado.

Como para focusInTouchModeambas cosas AbsListView y RecyclerView llama al método setFocusableInTouchMode(true); en su constructor de forma predeterminada, por lo que no es necesario utilizar ese atributo en sus diseños XML.

Y para NestedScrollView se utiliza el siguiente método:

private void initScrollView() {
        mScroller = ScrollerCompat.create(getContext(), null);
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

Aquí, setFocusable() se utiliza el método en lugar de setFocusableInTouchMode(). Pero según esta publicación, focusableInTouchMode debe evitarse a menos que se den ciertas condiciones, ya que rompe la coherencia con el comportamiento normal de Android. Un juego es un buen ejemplo de una aplicación que puede hacer un buen uso de la propiedad enfocable en modo táctil. MapView, si se usa en pantalla completa como en Google Maps, es otro buen ejemplo de dónde puede usar correctamente el modo táctil enfocable.

  • ¡Gracias! Funciona en mi caso. Desafortunadamente, android:focusableInTouchMode=”true” no funcionó para mí.

    – Mijaíl

    13 de febrero de 2017 a las 14:09

  • Esto funcionó para mí. Agregué esta línea de código a la vista principal de mi vista de reciclador (en mi caso, era un LinearLayout) y funcionó de maravilla.

    – Amzer

    25 de mayo de 2017 a las 4:45

  • Estaba usando NestedScrollView seguido de LinearLayout que contiene RecyclerView y EditText. El comportamiento de desplazamiento se solucionó usando android:descendantFocusability=”blocksDescendants” dentro de LinearLayout, pero EditText no puede obtener el foco. ¿Alguna idea de lo que está pasando?

    – Sagar Chapagain

    26 de junio de 2017 a las 5:17

  • @SagarChapagain Es propiedad de blocksDescendants para bloquear el enfoque de todos los descendientes. No estoy seguro, pero intente beforeDescendants

    – Jim Patel

    26 de junio de 2017 a las 11:10

  • @JimitPatel mi problema se resolvió usando recyclerView.setFocusable(false); nestedScrollView.requestFocus();

    – Sagar Chapagain

    26 de junio de 2017 a las 11:26


android:descendantFocusability="blocksDescendants"

dentro de LinearLayout Funcionó para mí.

  • Pero, ¿significa que se bloqueará el enfoque en las vistas internas y los usuarios no podrán acceder a ellas?

    – desarrollador de Android

    10 de agosto de 2017 a las 11:36

Tuve el mismo problema y lo resolví al extender NestedScrollView y deshabilitar el enfoque de los niños. Por alguna razón, RecyclerView siempre solicitaba atención incluso cuando abría y cerraba el cajón.

public class DummyNestedScrollView extends NestedScrollView {
public DummyNestedScrollView(Context context) {
    super(context);
}

public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

/**
 * Fixind problem with recyclerView in nested scrollview requesting focus
 * http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
 * @param child
 * @param focused
 */
@Override
public void requestChildFocus(View child, View focused) {
    Log.d(getClass().getSimpleName(), "Request focus");
    //super.requestChildFocus(child, focused);

}


/**
 * http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
 * @param direction
 * @param previouslyFocusedRect
 * @return
 */
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    Log.d(getClass().getSimpleName(), "Request focus descendants");
    //return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    return false;
}
}

En mi caso, este código resuelve el problema mío.

RecyclerView recyclerView = findViewById(R.id.recyclerView);
NestedScrollView nestedScrollView= findViewById(R.id.nestedScrollView);

recyclerView.setFocusable(false);
nestedScrollView.requestFocus();

//populate recyclerview here

Mi diseño contiene un diseño principal como NestedScrollView que tiene un LinearLayout secundario. LinearLayout tiene orientación “vertical” y childs RecyclerView y EditText. Referencia

avatar de usuario
Gilbert Parreño

Solo agrega android:descendantFocusability="blocksDescendants" en ViewGroup dentro de NestedScrollView.

Tengo dos conjeturas.

Primero: intente poner esta línea en su NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Segundo: Uso

<android.support.design.widget.CoordinatorLayout

como su vista principal Me gusta esto

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:orientation="vertical"
                      android:padding="16dp">

            <TextView
                style="@style/TextAppearance.AppCompat.Caption"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Title:"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Body1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="@dimen/bodyPadding"
                android:text="Neque porro quisquam est qui dolorem ipsum"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Caption"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Subtitle:"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Body1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="@dimen/bodyPadding"
                android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>

        </LinearLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:focusable="false"/>

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

Mi última solución posible. Lo juro 🙂

  • Y agregar el diseño del coordinador como la vista principal tampoco funcionó … Gracias de todos modos

    – Lucas Correa

    30 de marzo de 2016 a las 19:08

¿Ha sido útil esta solución?