Widok Recycler wewnątrz NestedScrollView powoduje, że przewijanie rozpoczyna się w środku

129

Otrzymuję dziwne zachowanie przewijania, gdy dodaję RecyclerView do NestedScrollView.

Dzieje się tak, że ilekroć widok przewijania ma więcej wierszy, niż można wyświetlić na ekranie, zaraz po uruchomieniu działania NestedScrollView zaczyna się od przesunięcia od góry (obraz 1). Jeśli w widoku przewijania jest niewiele elementów, tak że można je wszystkie wyświetlić jednocześnie, tak się nie dzieje (obraz 2).

Używam wersji 23.2.0 biblioteki obsługi.

Obraz 1 : ŹLE - zaczyna się od przesunięcia od góry

Zdjęcie 1

Zdjęcie 2 : PRAWIDŁOWO - kilka pozycji w widoku recyklera

Ryc.2

Wklejam poniżej mój kod układu:

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

Czy coś mi brakuje? Czy ktoś ma pomysł, jak to naprawić?

Zaktualizuj 1

Działa poprawnie, jeśli podczas inicjowania działania umieszczę następujący kod:

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

Gdzie sv jest odwołaniem do NestedScrollView, jednak wygląda na to, że to niezły hack.

Zaktualizuj 2

Zgodnie z żądaniem, oto mój kod adaptera:

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

A oto mój 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);
    }
}

A oto układ każdego elementu w RecyclerView (po prostu android.R.layout.simple_spinner_item- ten ekran służy tylko do pokazania przykładu tego błędu):

<?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
źródło
Próbowałem z android:focusableInTouchMode="false"za RecyclerView? smth wyraźnie jest „zmuszanie układ, aby przejść do dołu ... (gdzie zacząć, gdy masz 1295 pozycji na dole lub po prostu mały top offsetowych jak pierwszego ekranu?)
snachmsm
Ustaw android: clipToPadding = „true” na swój NestedScrollView.
natario
także: utrzymywanie przewijalnego widoku w innym widoku przewijanym w ten sam sposób nie jest dobrym wzorcem ... mam nadzieję, że używasz właściwegoLayoutManager
snachmsm
Wypróbowałem obie sugestie i niestety żadna nie zadziałała. @snachmsm bez względu na liczbę elementów w widoku recyklera, przesunięcie jest zawsze takie samo. Jeśli chodzi o to, czy umieszczenie RecyclerView wewnątrz NestedScrollView jest dobrym wzorcem, w rzeczywistości zostało to zalecane przez inżyniera Google na plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa,
1
Nawet mam ten sam problem podczas używania RecyclerView w NestedScrollView. To zły wzór, ponieważ sam wzór recyklera nie zadziała. Wszystkie widoki zostaną narysowane naraz (ponieważ WRAP_CONTENT wymaga wysokości widoku recyklera). Nie będzie żadnego recyklingu widoku w tle, więc główny cel samego widoku recyklera nie działa. Ale zarządzanie danymi i rysowanie układu przy użyciu widoku recyklera jest łatwe, to jedyny powód, dla którego możesz użyć tego wzorca. Dlatego lepiej nie używać go, dopóki nie jest to konieczne.
Ashok Varma

Odpowiedzi:

253

Rozwiązałem taki problem ustawiając:

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

do mojego widoku powyżej RecyclerView (który został ukryty po niechcianym przewijaniu). Spróbuj ustawić tę właściwość na LinearLayout powyżej RecyclerView lub na LinearLayout, który jest kontenerem RecyclerView (pomógł mi w innym przypadku).

Jak widzę w źródle NestedScrollView, próbuje skupić się na pierwszym możliwym dziecku w onRequestFocusInDescendants i jeśli tylko RecyclerView jest aktywny, wygrywa.

Edycja (dzięki Waran): a dla płynnego przewijania nie zapomnij ustawić yourRecyclerView.setNestedScrollingEnabled(false);

Dmitry Gavrilko
źródło
12
Przy okazji, musisz dodać yourRecyclerView.setNestedScrollingEnabled (false); w celu płynnego przewijania
Waran
1
@Kenji tylko do pierwszego widoku wewnątrz LinearLayout, w którym znajduje się RecyclerView. Lub do samego LinearLayout (kontener RecyclerView), jeśli pierwsza zmiana nie pomoże.
Dmitry Gavrilko
1
@DmitryGavrilko Mam problem, w którym RecycleView jest wewnątrz NestedScrollview. Nie mogę użyć recyclingleview.scrollToPosition (X); , to po prostu nie działa. Próbowałem wszystkiego w ciągu ostatnich 6 dni, ale mogę się z tym pogodzić. jakieś sugestie? Byłbym bardzo wdzięczny!
Karoly
3
Możesz również po prostu ustawić widok android:focusableInTouchMode="false"recyklingu, aby nie trzeba było ustawiać wartości true dla wszystkich innych widoków.
Aksiom,
1
Zgadzam się z Dmitrijem Gavrilko, pracował po ustawieniu android: focusableInTouchMode = "true" na linearlayout, który zawiera recyclinglerview. @Aksiom ustawienie android: focusableInTouchMode = "false" do recyclinglerView nie działa dla mnie.
Shendre Kiran
103

W swoim LinearLayoutbezpośrednim PO NestedScrollView, wykorzystanie android:descendantFocusabilityw następujący sposób

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

EDYTOWAĆ

Ponieważ wielu z nich uzyskało użyteczną odpowiedź, poda również wyjaśnienie.

Zastosowanie descendantFocusabilitypodano tutaj . A od focusableInTouchModeponad tutaj . Więc korzystając blocksDescendantsz descendantFocusabilitynie pozwala, że to dziecko, aby uzyskać ostrość podczas dotykania i stąd nieplanowane zachowanie może być zatrzymany.

Jeśli chodzi o focusInTouchMode, zarówno AbsListViewi domyślnie RecyclerViewwywołuje metodę setFocusableInTouchMode(true);w ich konstruktorze, więc nie jest wymagane używanie tego atrybutu w układach XML.

I NestedScrollViewstosuje się następującą metodę:

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

Tutaj setFocusable()zamiast metody używana jest metoda setFocusableInTouchMode(). Ale według tego postu , focusableInTouchModenależy unikać, chyba że na pewnych warunkach, gdyż łamie zgodność z Androidem normalne zachowanie. Gra jest dobrym przykładem aplikacji, która może dobrze wykorzystać właściwość, którą można ustawić w trybie dotykowym. MapView, jeśli jest używany w trybie pełnoekranowym, tak jak w Mapach Google, jest kolejnym dobrym przykładem tego, gdzie można poprawnie używać ustawiania ostrości w trybie dotykowym.

Jimit Patel
źródło
Dziękuję Ci! W moim przypadku działa. Niestety android: focusableInTouchMode = "true" nie działał dla mnie.
Michaił
1
To zadziałało dla mnie. Dodałem tę linię kodu do widoku nadrzędnego mojego widoku recyklingu (w moim przypadku był to układ LinearLayout) i działał jak urok.
amzer
Używałem NestedScrollView, a następnie LinearLayout, który zawiera RecyclerView i EditText. Zachowanie przewijania zostało naprawione przez użycie android: descendantFocusability = "blocksDescendants" wewnątrz LinearLayout, ale EditText nie może uzyskać fokusu. Masz pojęcie, co się dzieje?
Sagar Chapagain
@SagarChapagain Jest to właściwość blocksDescendantsdo blokowania wszystkich elementów podrzędnych . Nie jestem pewien, ale spróbujbeforeDescendants
Jimit Patel
2
@JimitPatel mój problem został rozwiązany za pomocąrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain
16
android:descendantFocusability="blocksDescendants"

wewnątrz LinearLayout zadziałało dla mnie.

Subash Bhattarai
źródło
6
Ale czy to oznacza, że ​​skupienie się na widokach wewnątrz zostanie zablokowane, a użytkownicy nie będą mogli do nich dotrzeć?
programista Androida
11

Miałem ten sam problem i rozwiązałem go, rozszerzając NestedScrollView i wyłączając skupianie dzieci. Z jakiegoś powodu RecyclerView zawsze żądał skupienia, nawet gdy właśnie otworzyłem i zamknąłem szufladę.

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
źródło
Działa, ale łamie również koncepcję skupienia i możesz łatwo uzyskać sytuację dzięki dwóm skupionym polom na ekranie. Więc używaj go tylko wtedy, gdy nie masz żadnych elementów EditText wewnątrz RecyclerViewposiadaczy.
Andrey Chernoprudov
4

W moim przypadku ten kod rozwiązuje mój problem

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

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

//populate recyclerview here

Mój układ zawiera układ nadrzędny jako NestedScrollView, który ma element podrzędny LinearLayout. LinearLayout ma orientację „pionową” i podrzędne RecyclerView i EditText. Odniesienie

Sagar Chapagain
źródło
2

Mam dwa przypuszczenia.

Po pierwsze: spróbuj umieścić tę linię w swoim NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Po drugie: użyj

<android.support.design.widget.CoordinatorLayout

jak widzą twoi rodzice

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

Moje ostatnie możliwe rozwiązanie. Przysięgam :)

Andre Haueisen
źródło
I dodanie CoordinatorLayout jako widoku nadrzędnego też nie zadziałało ... Dzięki mimo wszystko
Luccas Correa
2

Ten problem pojawia się z powodu ostrości widoku recyklingu.

Automatycznie cały fokus przechodzi do widoku ponownego wyświetlania, jeśli jego rozmiar zwiększył rozmiar ekranu.

dodanie android:focusableInTouchMode="true"do pierwszego ChildView podobnego TextView, Buttoni tak dalej (nie ViewGroupjak Linear, Relativei tak dalej) ma sens, aby rozwiązać problem, ale API Level 25 i powyżej nie rozwiązuje problemu.

Wystarczy dodać te 2 linie w ChildView podobnego TextView, Buttoni tak dalej (nie ViewGroupjak Linear, Relativei tak dalej)

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

Właśnie napotkałem ten problem na poziomie API 25. Mam nadzieję, że inni ludzie nie marnują na to czasu.

Aby uzyskać płynne przewijanie w RecycleView, dodaj tę linię

 android:nestedScrollingEnabled="false"

ale dodanie tego atrybutu działa tylko z poziomem API 21 lub wyższym. Jeśli chcesz, aby płynne przewijanie działało poniżej poziomu API 25, dodaj tę linię do swojej klasy

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);
sushildlh
źródło
0

W kodzie Java, po zainicjowaniu programu recyclinglerView i ustawieniu adaptera, dodaj następujący wiersz:

recyclerView.setNestedScrollingEnabled(false)

Możesz również spróbować zawinąć układ za pomocą względnego układu, aby widoki pozostały w tej samej pozycji, ale recyklerView (który przewija) jest pierwszy w hierarchii XML. Ostatnia sugestia to desperacka próba: s

johnny_crq
źródło
0

Ponieważ spóźniam się z odpowiedzią, ale mogę pomóc komuś innemu. Po prostu użyj niższej lub wyższej wersji w build.gradle na poziomie aplikacji, a problem zostanie usunięty.

compile com.android.support:recyclerview-v7:23.2.1
Amit
źródło
0

aby przewinąć do góry, po prostu zadzwoń setcontentview:

scrollView.SmoothScrollTo(0, 0);
user3844824
źródło
to nie daje odpowiedzi na pytanie
Umar Ata
0

Po prostu dodaj android:descendantFocusability="blocksDescendants"ViewGroup wewnątrz NestedScrollView.

Gilbert Parreno
źródło