La vue Recycler dans NestedScrollView provoque le démarrage du défilement au milieu


130

J'obtiens un comportement de défilement étrange lorsque j'ajoute un RecyclerView dans un NestedScrollView.

Ce qui se passe, c'est que chaque fois que la vue de défilement a plus de lignes que ce qui peut être affiché à l'écran, dès que l'activité est lancée, NestedScrollView commence par un décalage par rapport au haut (image 1). S'il y a peu d'éléments dans la vue de défilement pour qu'ils puissent tous être affichés en même temps, cela ne se produit pas (image 2).

J'utilise la version 23.2.0 de la bibliothèque de support.

Image 1 : MAUVAIS - commence par un décalage à partir du haut

Image 1

Image 2 : CORRECT - quelques articles dans la vue du recycleur

Image 2

Je colle sous mon code de mise en page:

<?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>

Est-ce que je manque quelque chose? Quelqu'un at-il une idée de la façon de résoudre ce problème?

Mise à jour 1

Cela fonctionne correctement si je place le code suivant lors de l'initialisation de mon activité:

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

Où sv est une référence à NestedScrollView, mais cela ressemble à un hack.

Mise à jour 2

Comme demandé, voici mon code d'adaptateur:

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

Et voici mon 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);
    }
}

Et voici la disposition de chaque élément dans RecyclerView (c'est juste android.R.layout.simple_spinner_item- cet écran est uniquement pour montrer un exemple de ce bogue):

<?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"/>

essayé avec android:focusableInTouchMode="false"pour RecyclerView? smth "oblige clairement votre mise en page à aller en bas ... (où elle commence lorsque vous avez 1295 éléments? bas ou juste un petit décalage supérieur comme le premier écran)
snachmsm

Réglez android: clipToPadding = "true" sur votre NestedScrollView.
natario

aussi: garder une vue défilante dans une autre vue défilante de la même manière n'est pas très bon modèle ... j'espère que vous utilisez correctementLayoutManager
snachmsm

J'ai essayé vos deux suggestions et malheureusement aucune n'a fonctionné. @snachmsm quel que soit le nombre d'éléments dans la vue du recycleur, le décalage est toujours le même. Quant à savoir si placer un RecyclerView à l'intérieur d'un NestedScrollView est un bon modèle, cela a en fait été recommandé par un ingénieur Google à plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa

1
Même j'ai le même problème lors de l'utilisation de RecyclerView dans NestedScrollView. C'est un mauvais modèle parce que le modèle du recycleur lui-même ne fonctionnera pas. Toutes les vues seront dessinées à la fois (puisque WRAP_CONTENT a besoin de la hauteur de la vue du recycleur). Il n'y aura pas de recyclage de vue en arrière-plan, donc l'objectif principal de la vue du recycleur lui-même ne fonctionne pas. Mais il est facile de gérer les données et de dessiner la mise en page en utilisant la vue Recycler, c'est la seule raison pour laquelle vous pouvez utiliser ce modèle. Il vaut donc mieux ne pas l'utiliser jusqu'à ce que vous en ayez besoin à coup sûr.
Ashok Varma

Réponses:


253

J'ai résolu ce problème en définissant:

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

à mon avis au-dessus de RecyclerView (qui était caché après un défilement indésirable). Essayez de définir cette propriété sur votre LinearLayout au-dessus de RecyclerView ou sur LinearLayout qui est le conteneur de RecyclerView (m'a aidé dans un autre cas).

Comme je le vois dans la source NestedScrollView, il essaie de concentrer le premier enfant possible dans onRequestFocusInDescendants et si seul RecyclerView est focalisable, il gagne.

Modifier (grâce à Waran): et pour un défilement fluide, n'oubliez pas de définir yourRecyclerView.setNestedScrollingEnabled(false);


12
En passant, vous devez ajouter yourRecyclerView.setNestedScrollingEnabled (false); afin de rendre le défilement lisse
Waran-

1
@Kenji uniquement pour la première vue à l'intérieur de LinearLayout où RecyclerView est placé. Ou à ce LinearLayout (conteneur RecyclerView) lui-même si le premier changement n'aide pas.
Dmitry Gavrilko

1
@DmitryGavrilko J'ai un problème, où le RecycleView est à l'intérieur d'un NestedScrollview. Je ne peux pas utiliser recycleview.scrollToPosition (X); , cela ne fonctionne tout simplement pas. J'ai tout essayé ces 6 derniers jours, mais je peux m'en remettre. toute suggestion? Je serais très reconnaissant !
Karoly

3
Vous pouvez également simplement définir android:focusableInTouchMode="false"sur recyclerView afin que vous n'ayez pas besoin de définir true pour toutes les autres vues.
Aksiom

1
D'accord avec Dmitry Gavrilko, a travaillé après avoir défini android: focusableInTouchMode = "true" sur la disposition linéaire qui contient recyclerview. @Aksiom définissant android: focusableInTouchMode = "false" sur le recyclerView ne fonctionne pas pour moi.
Shendre Kiran le

103

Dans votre LinearLayoutimmédiatement après NestedScrollView, utilisez android:descendantFocusabilityde la manière suivante

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

ÉDITER

Étant donné que beaucoup d'entre eux obtiennent cette réponse utile, fournira également des explications.

L'utilisation de descendantFocusabilityest donnée ici . Et comme de focusableInTouchModeplus ici . Donc, utiliser blocksDescendantsdans descendantFocusabilityne permet pas à son enfant de se concentrer tout en touchant et donc un comportement imprévu peut être arrêté.

Quant à focusInTouchMode, les deux AbsListViewet RecyclerViewappelle la méthode setFocusableInTouchMode(true);dans leur constructeur par défaut, il n'est donc pas nécessaire d'utiliser cet attribut dans vos mises en page XML.

Et pour la NestedScrollViewméthode suivante est utilisée:

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

Ici, la setFocusable()méthode est utilisée à la place de setFocusableInTouchMode(). Mais selon ce post , focusableInTouchModedevrait être évité sauf dans certaines conditions, car cela rompt la cohérence avec le comportement normal d'Android. Un jeu est un bon exemple d'application qui peut faire bon usage de la propriété focusable en mode tactile. MapView, s'il est utilisé en plein écran comme dans Google Maps, est un autre bon exemple où vous pouvez utiliser correctement la mise au point en mode tactile.


Je vous remercie! Cela fonctionne dans mon cas. Malheureusement, Android: focusableInTouchMode = "true" ne fonctionnait pas pour moi.
Mikhail

1
Cela a fonctionné pour moi. J'ai ajouté cette ligne de code à la vue parente de mon recyclerview (dans mon cas, c'était un LinearLayout) et cela a fonctionné comme un charme.
amzer

J'utilisais NestedScrollView suivi de LinearLayout qui contient RecyclerView et EditText. Le comportement de défilement a été corrigé en utilisant android: descendantFocusability = "blocksDescendants" dans LinearLayout mais EditText ne peut pas obtenir le focus. Une idée de ce qui se passe?
Sagar Chapagain

@SagarChapagain C'est la propriété de blocksDescendantsbloquer le focus de tous les descendants. Je ne suis pas sûr, mais essayezbeforeDescendants
Jimit Patel

2
@JimitPatel mon problème a été résolu en utilisantrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain

16
android:descendantFocusability="blocksDescendants"

à l'intérieur de LinearLayout a fonctionné pour moi.


6
Mais cela signifie-t-il que la concentration sur les vues à l'intérieur sera bloquée et que les utilisateurs ne pourront pas les atteindre?
développeur android

11

J'ai eu le même problème et j'ai résolu en étendant NestedScrollView et en désactivant la focalisation des enfants. Pour une raison quelconque, RecyclerView a toujours demandé la mise au point même lorsque je viens d'ouvrir et de fermer le tiroir.

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

Cela fonctionne mais cela rompt également le concept de mise au point et vous pouvez facilement obtenir une situation avec deux champs focalisés sur l'écran. Alors utilisez-le uniquement si vous n'avez pas de support EditText à l'intérieur RecyclerView.
Andrey Chernoprudov

4

Dans mon cas, ce code résout le problème du mien

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

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

//populate recyclerview here

Ma mise en page contient une mise en page parent en tant que NestedScrollView qui a un enfant LinearLayout. Le LinearLayout a une orientation "verticale" et les fils RecyclerView et EditText. Référence


2

J'ai deux suppositions.

Premièrement: essayez de mettre cette ligne sur votre NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Deuxièmement: utiliser

<android.support.design.widget.CoordinatorLayout

comme votre parent le voit comme ça

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

Ma dernière solution possible. Je le jure :)


Et l'ajout de CoordinatorLayout en tant que vue parent n'a pas fonctionné non plus ... Merci
quand même

2

Ce problème survient en raison de la vue de recyclage Focus.

Automatiquement, tout le focus est passé à la vue de recyclage si sa taille étendait la taille de l'écran.

l'ajout android:focusableInTouchMode="true"au premier ChildView comme TextView, Buttonet ainsi de suite (pas ViewGroupcomme Linear, Relativeet ainsi de suite) a du sens pour résoudre le problème, mais la solution de niveau d'API 25 et plus ne fonctionne pas.

Ajoutez simplement ces 2 lignes dans votre ChildView comme TextView, Buttonet ainsi de suite (pas sur ViewGroupcomme Linear, Relativeet ainsi de suite)

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

Je viens de faire face à ce problème au niveau de l'API 25. J'espère que d'autres personnes ne perdront pas de temps là-dedans.

Pour un défilement fluide sur RecycleView, ajoutez cette ligne

 android:nestedScrollingEnabled="false"

mais l'ajout de ces attiributes ne fonctionne qu'avec le niveau d'API 21 ou supérieur. Si vous voulez que le défilement de lissage fonctionne au-dessous du niveau d'API 25, ajoutez cette ligne dans votre classe

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

0

Dans le code Java, après avoir initialisé votre recyclerView et défini l'adaptateur, ajoutez cette ligne:

recyclerView.setNestedScrollingEnabled(false)

Vous pouvez également essayer d'encapsuler la mise en page avec un relativeLayout afin que les vues restent à la même position, mais recyclerView (qui défile) est le premier dans la hiérarchie XML. La dernière suggestion une tentative désespérée: p


0

Comme je suis en retard pour répondre, mais peut aider quelqu'un d'autre. Utilisez simplement la version ci-dessous ou supérieure dans votre build.gradle au niveau de l'application et le problème est supprimé.

compile com.android.support:recyclerview-v7:23.2.1

0

pour faire défiler vers le haut, il suffit d'appeler ceci setcontentview:

scrollView.SmoothScrollTo(0, 0);

cela ne répond pas à la question
Umar Ata

0

Ajoutez simplement android:descendantFocusability="blocksDescendants"le ViewGroup à l'intérieur du NestedScrollView.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.