web-dev-qa-db-fra.com

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

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 l'écran de défilement comporte 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 pouvoir tous les afficher 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 : WRONG - commence par un décalage par rapport au haut

Image 1

Image 2 : CORRECT - Quelques éléments dans la vue Recycleur

Image 2

Je colle en dessous de 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 comment 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 bidouillage.

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 de RecyclerView (il ne s'agit que de Android.R.layout.simple_spinner_item - cet écran sert uniquement à 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"/>
104
Luccas Correa

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

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

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

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

Edit (merci à Waran): et pour un défilement régulier, n'oubliez pas de définir yourRecyclerView.setNestedScrollingEnabled(false);

203
Dmitry Gavrilko

Dans votre LinearLayout immédiatement après NestedScrollView, utilisez Android:descendantFocusability de la manière suivante

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

MODIFIER

Étant donné que beaucoup d’entre eux ont obtenu cette réponse utile, fourniront également des explications.

L'utilisation de descendantFocusability est donnée ici . Et à partir de focusableInTouchMode sur here . Donc, utiliser blocksDescendants dans descendantFocusability ne permet pas à son enfant d’acquérir le focus tout en touchant et donc un comportement non prévu peut être arrêté.

En ce qui concerne focusInTouchMode, AbsListView et RecyclerView appelle la méthode setFocusableInTouchMode(true); dans leur constructeur par défaut. Il n'est donc pas nécessaire d'utiliser cet attribut dans vos présentations XML.

Et pour NestedScrollView la mé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 méthode setFocusable() est utilisée à la place de setFocusableInTouchMode(). Mais selon ce post , focusableInTouchMode devrait être évité, sauf dans certaines conditions, car cela brise la cohérence avec le comportement normal d'Android. Un jeu est un bon exemple d’application qui peut exploiter au mieux les propriétés de mise au point en mode tactile. MapView, s’il est utilisé en plein écran comme dans Google Maps, est un autre bon exemple d’utilisation correcte de la mise au point en mode tactile.

81
Jimit Patel

J'ai eu le même problème et résolu en étendant NestedScrollView et en désactivant les enfants qui se focalisaient. Pour une raison quelconque, RecyclerView a toujours demandé la mise au point même lorsque je venais 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;
}
}
10
Lubos Horacek
Android:descendantFocusability="blocksDescendants"

inside LinearLayout A travaillé pour moi.

8
Subash Bhattarai

Dans mon cas, ce code résout le problème des mines

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

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

//populate recyclerview here

Ma disposition contient une disposition parent comme NestedScrollView qui a un enfant LinearLayout. Le LinearLayout a une orientation "verticale" et les fils RecyclerView et EditText. Référence

3
Sagar Chapagain

J'ai deux suppositions. 

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

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Deuxièmement: Use 

<Android.support.design.widget.CoordinatorLayout

en tant que vue parent.

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

1
Andre Haueisen

En 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 disposition avec un relativeLayout afin que les vues restent à la même position mais que recyclerView (qui fait défiler) se trouve d'abord dans la hiérarchie xml. La dernière suggestion une tentative désespérée: p

1
johnny_crq

Ce problème arrive en raison de recycler voir Focus.

Recycle automatiquement l'affichage de tous les focus si sa taille augmentait celle de l'écran.

ajouter Android:focusableInTouchMode="true" au premier ChildView comme TextView, Button et ainsi de suite (pas sur ViewGroup comme Linear, Relative et ainsi de suite) ont du sens pour résoudre le problème mais la solution API de niveau 25 et supérieur ne fonctionne pas. 

Ajoutez simplement ces 2 lignes dans votre ChildView telles que TextView, Button et ainsi de suite (Pas sur ViewGroup comme Linear, Relative et ainsi de suite)

 Android:focusableInTouchMode="true"
 Android:focusable="true"

Je viens juste de faire face à ce problème au niveau API 25. J'espère que les autres personnes ne perdent pas de temps à cela.

Pour un défilement régulier sur RecycleView, ajoutez cette ligne  

 Android:nestedScrollingEnabled="false"

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

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

Comme je suis en retard dans ma réponse, mais que je peux aider quelqu'un d’autre… .. Utilisez simplement la version ci-dessous ou une version plus récente dans votre version de build.gradle au niveau de l’application et le problème est résolu.

compile com.Android.support:recyclerview-v7:23.2.1
0
Amit

pour faire défiler vers le haut, appelez simplement ceci dans setcontentview:

scrollView.SmoothScrollTo(0, 0);
0
user3844824