Die Recycler-Ansicht in NestedScrollView bewirkt, dass der Bildlauf in der Mitte beginnt

129

Ich erhalte ein seltsames Bildlaufverhalten, wenn ich eine RecyclerView in eine NestedScrollView einfüge.

Wenn die Bildlaufansicht mehr Zeilen enthält, als auf dem Bildschirm angezeigt werden können, beginnt die NestedScrollView nach dem Start der Aktivität mit einem Versatz von oben (Bild 1). Wenn die Bildlaufansicht nur wenige Elemente enthält, sodass alle gleichzeitig angezeigt werden können, geschieht dies nicht (Bild 2).

Ich verwende Version 23.2.0 der Support-Bibliothek.

Bild 1 : FALSCH - beginnt mit einem Versatz von oben

Bild 1

Bild 2 : RICHTIG - wenige Elemente in der Recycler-Ansicht

Bild 2

Ich füge unter meinem Layoutcode ein:

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

Vermisse ich etwas Hat jemand eine Idee, wie man das behebt?

Update 1

Es funktioniert ordnungsgemäß, wenn ich beim Initialisieren meiner Aktivität den folgenden Code eingebe:

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

Wobei sv ein Verweis auf NestedScrollView ist, sieht es jedoch nach einem ziemlichen Hack aus.

Update 2

Wie gewünscht, hier ist mein Adaptercode:

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

Und hier ist mein 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);
    }
}

Und hier ist das Layout jedes Elements in der RecyclerView (es ist nur android.R.layout.simple_spinner_item- dieser Bildschirm dient nur zur Anzeige eines Beispiels für diesen Fehler):

<?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
quelle
versucht mit android:focusableInTouchMode="false"für RecyclerView? smth ist eindeutig "zwingen Sie Ihr Layout, nach unten zu gehen ... (wo es beginnt, wenn Sie 1295 Elemente haben? unten oder nur kleiner oberer Versatz wie der erste Bildschirm)
snachmsm
Setze android: clipToPadding = "true" für deine NestedScrollView.
Natario
auch: die scrollbare Ansicht in einer anderen auf die gleiche Weise scrollbaren Ansicht zu halten, ist kein sehr gutes Muster ... hoffe, Sie verwenden das richtigeLayoutManager
snachmsm
Versuchte beide Vorschläge und leider hat keiner funktioniert. @snachmsm Unabhängig von der Anzahl der Elemente in der Recycler-Ansicht ist der Offset immer gleich. Ob das Platzieren einer RecyclerView in einer NestedScrollView ein gutes Muster ist, wurde von einem Google-Techniker unter plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa am
1
Sogar ich habe das gleiche Problem, wenn ich RecyclerView in NestedScrollView verwende. Es ist ein schlechtes Muster, weil das Recycler-Muster selbst nicht funktioniert. Alle Ansichten werden gleichzeitig gezeichnet (da WRAP_CONTENT die Höhe der Recycler-Ansicht benötigt). Im Hintergrund wird kein View-Recycling durchgeführt, sodass der Hauptzweck der Recycler-Ansicht selbst nicht funktioniert. Es ist jedoch einfach, Daten zu verwalten und Layouts mithilfe der Recycler-Ansicht zu zeichnen. Dies ist der einzige Grund, warum Sie dieses Muster verwenden können. Verwenden Sie es also besser erst, wenn Sie es sicher benötigen.
Ashok Varma

Antworten:

253

Ich habe dieses Problem gelöst, indem ich Folgendes eingestellt habe:

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

zu meiner Ansicht über RecyclerView (die nach unerwünschtem Scrollen ausgeblendet wurde). Versuchen Sie, diese Eigenschaft auf Ihr LinearLayout über RecyclerView oder auf LinearLayout festzulegen, das ein Container von RecyclerView ist (hat mir in einem anderen Fall geholfen).

Wie ich in der NestedScrollView-Quelle sehe, versucht es, das erste mögliche Kind in onRequestFocusInDescendants zu fokussieren, und wenn nur RecyclerView fokussierbar ist, gewinnt es.

Bearbeiten (danke an Waran): und für einen reibungslosen Bildlauf vergessen Sie nicht einzustellen yourRecyclerView.setNestedScrollingEnabled(false);

Dmitry Gavrilko
quelle
12
Übrigens müssen Sie yourRecyclerView.setNestedScrollingEnabled (false) hinzufügen. um die Schriftrolle glatt zu machen
Waran
1
@ Kenji nur, um zuerst innerhalb von LinearLayout anzuzeigen, wo RecyclerView platziert ist. Oder zu diesem LinearLayout (RecyclerView-Container) selbst, wenn die erste Änderung nicht hilft.
Dmitry Gavrilko
1
@DmitryGavrilko Ich habe ein Problem, bei dem sich die RecycleView in einer NestedScrollview befindet. Ich kann recycleview.scrollToPosition (X) nicht verwenden. , es funktioniert einfach nicht. Ich habe in den letzten 6 Tagen alles versucht, aber ich kann darüber hinwegkommen. irgendein Vorschlag? Ich wäre sehr dankbar !
Karoly
3
Sie können auch einfach android:focusableInTouchMode="false"die recyclerView festlegen , damit Sie nicht für alle anderen Ansichten true festlegen müssen.
Aksiom
1
Stimmen Sie mit Dmitry Gavrilko überein, der nach dem Setzen von android gearbeitet hat: focusableInTouchMode = "true" für das lineare Layout, das recyclerview enthält. @Aksiom Einstellung android: focusableInTouchMode = "false" für die recyclerView hat bei mir nicht funktioniert.
Shendre Kiran
103

Verwenden Sie es LinearLayoutunmittelbar danach wie folgtNestedScrollViewandroid:descendantFocusability

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

BEARBEITEN

Da viele von ihnen diese Antwort nützlich finden, werden sie auch Erklärungen geben.

Die Verwendung von descendantFocusabilitywird hier angegeben . Und als der focusableInTouchModeüber hier . Die Verwendung von blocksDescendantsin descendantFocusabilityermöglicht es dem Kind also nicht, sich beim Berühren zu konzentrieren, und daher kann ungeplantes Verhalten gestoppt werden.

Wie für focusInTouchModebeide AbsListViewund RecyclerViewruft die Methode setFocusableInTouchMode(true);in ihrem Konstruktor standardmäßig so ist es nicht das Attribut in der XML - Layout zu verwenden , erforderlich.

Und für NestedScrollViewfolgende Methode wird verwendet:

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

Hier setFocusable()wird anstelle von Methode verwendet setFocusableInTouchMode(). Aber nach diesem Post , focusableInTouchModesollte es sei denn , für bestimmte Bedingungen vermieden werden , da es die Übereinstimmung mit Android normales Verhalten bricht. Ein Spiel ist ein gutes Beispiel für eine Anwendung, die die im Touch-Modus fokussierbare Eigenschaft gut nutzen kann. Wenn MapView im Vollbildmodus wie in Google Maps verwendet wird, ist dies ein weiteres gutes Beispiel dafür, wo Sie im Touch-Modus fokussierbar verwenden können.

Jimit Patel
quelle
Danke dir! In meinem Fall funktioniert es. Leider hat Android: focusableInTouchMode = "true" bei mir nicht funktioniert.
Mikhail
1
Das hat bei mir funktioniert. Ich habe diese Codezeile zur übergeordneten Ansicht meiner Recycling-Ansicht hinzugefügt (in meinem Fall war es ein LinearLayout) und es hat wie ein Zauber funktioniert.
Amzer
Ich habe NestedScrollView verwendet, gefolgt von LinearLayout, das RecyclerView und EditText enthält. Das Bildlaufverhalten wurde mithilfe von android: descantFocusability = "blocksDescendants" in LinearLayout behoben, aber EditText kann keinen Fokus erhalten. Irgendeine Idee, was passiert?
Sagar Chapagain
@SagarChapagain Es ist die Eigenschaft blocksDescendants, den Fokus aller Nachkommen zu blockieren. Ich bin nicht sicher, aber versuchen SiebeforeDescendants
Jimit Patel
2
@ JimPatel mein Problem wurde gelöst mitrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain
16
android:descendantFocusability="blocksDescendants"

in LinearLayout Hat für mich funktioniert.

Subash Bhattarai
quelle
6
Aber bedeutet dies, dass der Fokus auf die Ansichten im Inneren blockiert wird und Benutzer sie nicht erreichen können?
Android-Entwickler
11

Ich hatte das gleiche Problem und habe NestedScrollView erweitert und fokussierende Kinder deaktiviert. Aus irgendeinem Grund hat RecyclerView immer um Fokus gebeten, auch wenn ich gerade die Schublade geöffnet und geschlossen habe.

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
quelle
Es funktioniert, aber es bricht auch das Fokuskonzept und Sie können leicht eine Situation mit zwei fokussierten Feldern auf dem Bildschirm erhalten. Verwenden Sie es daher nur, wenn Sie keinen EditText in den RecyclerViewHaltern haben.
Andrey Chernoprudov
4

In meinem Fall löst dieser Code mein Problem

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

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

//populate recyclerview here

Mein Layout enthält ein übergeordnetes Layout als NestedScrollView mit einem untergeordneten LinearLayout. Das LinearLayout hat die Ausrichtung "vertikal" und unterliegt RecyclerView und EditText. Referenz

Sagar Chapagain
quelle
2

Ich habe zwei Vermutungen.

Erstens: Versuchen Sie, diese Zeile in Ihre NestedScrollView einzufügen

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Zweitens: Verwenden

<android.support.design.widget.CoordinatorLayout

als Ihre Elternansicht So

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

Meine letzte mögliche Lösung. Ich schwöre :)

Andre Haueisen
quelle
Und das Hinzufügen des CoordinatorLayout als übergeordnete Ansicht hat auch nicht funktioniert ... Trotzdem danke
Luccas Correa
2

Dieses Problem tritt aufgrund des Fokus der Recyclingansicht auf.

Automatisch wird der gesamte Fokus auf die Wiederverwendung der Ansicht verschoben, wenn die Größe des Bildschirms vergrößert wurde.

Hinzufügen android:focusableInTouchMode="true"zum ersten ChildView wie TextView, Buttonund so weiter (nicht auf ViewGroupwie Linear, Relativeund so weiter) sinnvoll , das Problem aber API Stufe 25 und höher Lösung zu lösen funktioniert nicht.

Fügen Sie einfach diese 2 Zeile in ChildView wie TextView, Buttonund so weiter (nicht auf ViewGroupwie Linear, Relativeund so weiter)

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

Ich bin gerade auf API-Ebene 25 mit diesem Problem konfrontiert worden. Ich hoffe, andere Leute verschwenden keine Zeit damit.

Fügen Sie diese Zeile hinzu, um ein reibungsloses Scrollen in RecycleView zu ermöglichen

 android:nestedScrollingEnabled="false"

Das Hinzufügen dieser Attribute funktioniert jedoch nur mit API-Level 21 oder höher. Wenn Sie möchten, dass das Scrollen unter der API-Ebene 25 funktioniert, fügen Sie diese Zeile Ihrer Klasse hinzu

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

Fügen Sie im Java-Code nach dem Initialisieren von recyclerView und dem Festlegen des Adapters die folgende Zeile hinzu:

recyclerView.setNestedScrollingEnabled(false)

Sie können auch versuchen, das Layout mit einem relativen Layout zu umbrechen, damit die Ansichten an derselben Position bleiben, aber recyclerView (welcher Bildlauf) an erster Stelle in der XML-Hierarchie steht. Der letzte Vorschlag ein verzweifelter Versuch: p

johnny_crq
quelle
0

Da ich zu spät antworte, kann ich aber jemand anderem helfen. Verwenden Sie einfach die unten stehende oder höhere Version in build.gradle auf App-Ebene, und das Problem wird behoben.

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

Um nach oben zu scrollen, rufen Sie einfach Folgendes auf setcontentview:

scrollView.SmoothScrollTo(0, 0);
user3844824
quelle
Dies gibt keine Antwort auf die Frage
Umar Ata
0

android:descendantFocusability="blocksDescendants"Fügen Sie einfach die ViewGroup in der NestedScrollView hinzu.

Gilbert Parreno
quelle