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

130

Estoy obteniendo 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 de 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 que android.R.layout.simple_spinner_itemesta 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"/>
Luccas Correa
fuente
intentado con android:focusableInTouchMode="false"para RecyclerView? smth claramente está "obligando a su diseño a ir a la parte inferior ... (¿dónde comienza cuando tiene 1295 elementos? inferior o simplemente un pequeño desplazamiento superior como la primera pantalla)
snachmsm
Establezca android: clipToPadding = "true" en su NestedScrollView.
natario
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é usando el correctoLayoutManager
snachmsm
Intenté sus dos sugerencias y desafortunadamente ninguna funcionó. @snachmsm no importa el número de elementos en la vista de reciclador, el desplazamiento es siempre el mismo. En cuanto a si colocar un RecyclerView dentro de un NestedScrollView es un buen patrón, un ingeniero de Google lo ha recomendado en plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa
1
Incluso tengo el mismo problema cuando uso RecyclerView dentro de NestedScrollView. Es un mal patrón porque el patrón de reciclador en sí 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 vista en segundo plano, por lo que el objetivo principal de la vista del reciclador en sí no está funcionando. Pero es fácil administrar datos y dibujar diseños usando la vista de reciclador, esa es la única razón por la que puede usar este patrón. Así que es mejor no usarlo hasta que lo requiera con seguridad.
Ashok Varma

Respuestas:

253

Resolví tal problema estableciendo:

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

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

Como veo en la fuente NestedScrollView, trata de enfocar al primer hijo posible en onRequestFocusInDescendants y si solo RecyclerView es enfocable, gana.

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

Dmitry Gavrilko
fuente
12
Por cierto, debe agregar yourRecyclerView.setNestedScrollingEnabled (false); para hacer que el desplazamiento sea suave
Waran-
1
@Kenji solo para ver por primera vez dentro de LinearLayout donde se coloca RecyclerView. O a ese LinearLayout (contenedor RecyclerView) en sí mismo si el primer cambio no ayuda.
Dmitry Gavrilko
1
@DmitryGavrilko Tengo un problema, donde RecycleView está dentro de una vista de NestedScroll. No puedo usar recycleview.scrollToPosition (X); , simplemente no funciona. Lo intenté todo en los últimos 6 días, pero puedo superarlo. ¿cualquier sugerencia? Le estaría muy agradecido !
Karoly
3
También puede simplemente configurar android:focusableInTouchMode="false"a recyclerView para que no necesite establecer true para todas las demás vistas.
Aksiom
1
De acuerdo con Dmitry Gavrilko, trabajó después de configurar android: focusableInTouchMode = "true" en la distribución lineal que contiene la vista de reciclaje. La configuración de Android @Aksiom: focusableInTouchMode = "false" para el recyclerView no me funcionó.
Shendre Kiran
103

En su LinearLayoutdespués inmediata NestedScrollView, el uso android:descendantFocusabilityde 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 proporcionarán una explicación.

El uso de descendantFocusabilityse da aquí . Y a partir de focusableInTouchModemás de aquí . Por lo tanto, usar blocksDescendantsin descendantFocusabilityno permite que su hijo se enfoque mientras toca y, por lo tanto, se puede detener el comportamiento no planificado.

En cuanto a focusInTouchMode, ambos AbsListViewy RecyclerViewllama al método setFocusableInTouchMode(true);en su constructor de forma predeterminada, por lo que no es necesario usar ese atributo en sus diseños XML.

Y para el NestedScrollViewsiguiente método se utiliza:

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 usa el método en lugar de setFocusableInTouchMode(). Pero de acuerdo con esta publicación , focusableInTouchModedebe 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 enfoque en modo táctil.

Jimit Patel
fuente
¡Gracias! Funciona en mi caso. Lamentablemente, android: focusableInTouchMode = "true" no funcionó para mí.
Mikhail
1
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
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 enfocarse. ¿Alguna idea de lo que está pasando?
Sagar Chapagain
@SagarChapagain Es propiedad de blocksDescendantsbloquear el enfoque de todos los descendientes. No estoy seguro, pero pruebabeforeDescendants
Jimit Patel
2
@JimitPatel mi problema se resolvió usandorecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain el
16
android:descendantFocusability="blocksDescendants"

inside LinearLayout funcionó para mí.

Subash Bhattarai
fuente
66
Pero, ¿significa que se bloqueará el enfoque en las vistas internas y que los usuarios no podrán alcanzarlas?
Desarrollador de Android
11

Tuve el mismo problema y me molesté al extender NestedScrollView y deshabilitar enfocar a los niños. Por alguna razón, RecyclerView siempre solicitó enfoque incluso cuando abrí y cerré 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;
}
}
Lubos Horacek
fuente
Funciona, pero también rompe el concepto de enfoque y puede obtener fácilmente la situación con dos campos enfocados en la pantalla. Úselo solo si no tiene EditText dentro de los RecyclerViewtitulares.
Andrey Chernoprudov
4

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 primario como NestedScrollView que tiene un LinearLayout secundario. LinearLayout tiene orientación "vertical" y ChildRecyclerView y EditText. Referencia

Sagar Chapagain
fuente
2

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 la vista de tus padres 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 :)

Andre Haueisen
fuente
Y agregar CoordinatorLayout ya que la vista principal tampoco funcionó ... Gracias de todos modos
Luccas Correa
2

Este problema llega debido a la vista de reciclaje Focus.

Automáticamente, todo el foco se ha ido a la vista de reciclaje si su tamaño amplía el tamaño de la pantalla.

añadiendo android:focusableInTouchMode="true"al primer ChildView como TextView, Buttony así sucesivamente (no en ViewGroupcomo Linear, Relativey así sucesivamente) tiene sentido para resolver el problema, pero la API de nivel 25 y por encima de solución no funciona.

Simplemente agregue estas 2 líneas en su ChildView como TextView, Buttony así sucesivamente (No ViewGroupcomo Linear, Relativey así sucesivamente )

 android:focusableInTouchMode="true"
 android:focusable="true"

Acabo de enfrentar este problema en el nivel 25 de API. Espero que otras personas no pierdan el tiempo en esto.

Para un desplazamiento suave en RecycleView, agregue esta línea

 android:nestedScrollingEnabled="false"

pero agregar estos atributos solo funciona con el nivel de API 21 o superior. Si desea que el desplazamiento de suavizado funcione por debajo del nivel 25 de API, agregue esta línea en su clase

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);
sushildlh
fuente
0

En el código Java, después de inicializar su recyclerView y configurar el adaptador, agregue esta línea:

recyclerView.setNestedScrollingEnabled(false)

También puede intentar ajustar el diseño con un diseño relativo para que las vistas permanezcan en la misma posición pero recyclerView (que se desplaza) es el primero en la jerarquía xml. La última sugerencia es un intento desesperado: p

johnny_crq
fuente
0

Como llego tarde en responder, pero puede ayudar a alguien más. Simplemente use la versión siguiente o superior en el nivel de aplicación build.gradle y el problema se eliminará.

compile com.android.support:recyclerview-v7:23.2.1
Amit
fuente
0

para desplazarse a la parte superior, solo llame a esto en setcontentview:

scrollView.SmoothScrollTo(0, 0);
usuario3844824
fuente
esto no da respuesta a la pregunta
Umar Ata
0

Simplemente agregue android:descendantFocusability="blocksDescendants"el ViewGroup dentro de NestedScrollView.

Gilbert Parreno
fuente