Представление Recycler внутри NestedScrollView приводит к тому, что прокрутка начинается посередине

Я получаю странное поведение при прокрутке, когда добавляю RecyclerView внутри NestedScrollView.

Что происходит, так это то, что всякий раз, когда в scrollview есть больше строк, чем может быть показано на экране, как только действие запускается, NestedScrollView запускается со смещением сверху (изображение 1). Если в режиме прокрутки есть несколько элементов, так что все они могут быть показаны одновременно, этого не происходит (изображение 2).

Я использую версию 23.2.0 библиотеки поддержки.

Изображение 1: НЕПРАВИЛЬНО - начинается со смещения сверху

Изображение 1

Изображение 2: ПРАВИЛЬНО - в представлении ресайклера мало элементов.

Изображение 2

Я вставляю ниже свой код макета:

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

Я что-то упускаю? Кто-нибудь знает, как это исправить?

Обновление 1

Он работает правильно, если я помещаю следующий код при инициализации Activity:

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

Где sv - это ссылка на NestedScrollView, но это похоже на взлом.

Обновление 2

По запросу, вот мой код адаптера:

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

А вот и мой 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);
    }
}

А вот макет каждого элемента в RecyclerView (это просто android.R.layout.simple_spinner_item - этот экран предназначен только для демонстрации примера этой ошибки):

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

person Luccas Correa    schedule 30.03.2016    source источник
comment
пробовали с android:focusableInTouchMode="false" для RecyclerView? что-то явно заставляет ваш макет идти вниз ... (где он начинается, когда у вас 1295 элементов? снизу или просто небольшое верхнее смещение, как на первом экране)   -  person snachmsm    schedule 30.03.2016
comment
Установите android: clipToPadding = «true» в свой NestedScrollView.   -  person natario    schedule 30.03.2016
comment
также: сохранение прокручиваемого вида внутри другого вида с такой же прокруткой - не очень хороший шаблон ... надеюсь, вы используете правильный LayoutManager   -  person snachmsm    schedule 30.03.2016
comment
Пробовал оба ваших предложения, и, к сожалению, ни один из них не сработал. @snachmsm, независимо от количества элементов в представлении ресайклера, смещение всегда одно и то же. Что касается того, является ли размещение RecyclerView внутри NestedScrollView хорошим шаблоном, это на самом деле было рекомендовано инженером Google по адресу plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T   -  person Luccas Correa    schedule 30.03.2016
comment
Я могу исправить это, вызвав метод публикации NestedScrollView, я добавил его в свой вопрос. Но это похоже на противный взлом ...   -  person Luccas Correa    schedule 30.03.2016
comment
Это немного взломано, может быть, покажет нам фрагмент кода из дочернего макета адаптера и ресайклера. Также LayoutManager, используемый для RecyclerView, может быть полезным ... (я использовал тот же фрагмент на днях, но обнаружил фокус в дочернем элементе;))   -  person snachmsm    schedule 30.03.2016
comment
@snachmsm Я добавил код в свой вопрос, возможно, это поможет, но мне интересно, не является ли это внутренней ошибкой в ​​реализации NestedScrollView ...   -  person Luccas Correa    schedule 30.03.2016
comment
mObjects.add(object); notifyItemInserted(getItemCount() - 1); getItemCount должен быть получен перед добавлением, уведомлять только о новом;) возможно, перерисовка всего списка (что несущественно, нужно рисовать только новый элемент) вызывает это смещение и небольшую прокрутку. еще: LayoutManager также важно, если   -  person snachmsm    schedule 30.03.2016
comment
У меня точно такая же проблема. Спасибо за опубликованный хак. У меня есть макет во фрагменте, то есть в макете ящика. Он также устанавливает смещение для каждого закрытия ящика: - /   -  person Lubos Horacek    schedule 24.04.2016
comment
Даже у меня такая же проблема при использовании RecyclerView внутри NestedScrollView. Это плохой шаблон, потому что сам по себе шаблон ресайклера не работает. Все представления будут отрисованы одновременно (поскольку для WRAP_CONTENT требуется высота представления ресайклера). В фоновом режиме не будет повторного использования представления, поэтому основная цель самого представления recycler не работает. Но с помощью режима ресайклера легко управлять данными и рисовать макет, это единственная причина, по которой вы можете использовать этот шаблон. Так что лучше не использовать его до тех пор, пока он вам точно не понадобится.   -  person Ashok Varma    schedule 28.04.2016
comment
У меня проблема, когда RecycleView находится внутри NestedScrollview. Я не могу использовать recycleview.scrollToPosition (X); , это просто не работает. Я перепробовал все за последние 6 дней, но я могу это преодолеть. любое предложение? Буду очень благодарен!   -  person Karoly    schedule 06.01.2017
comment
У меня такая же проблема. Мы застряли на работе и используем RecyclerView внутри NestedScrollView. Я просто хотел бы подчеркнуть всем, что это полностью уничтожает все возможности утилизации. Все ваши просмотры для всего списка будут выложены при первом посещении экрана.   -  person Carson Holzheimer    schedule 21.06.2018
comment
Возможный дубликат Как убрать фокус с RecyclerView внутри ScrollView?   -  person lucidbrot    schedule 08.08.2019


Ответы (13)


Я решил такую ​​проблему, установив:

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

на мой взгляд выше RecyclerView (который был скрыт после нежелательной прокрутки). Попробуйте установить это свойство для своего LinearLayout над RecyclerView или для LinearLayout, который является контейнером RecyclerView (помог мне в другом случае).

Как я вижу в источнике NestedScrollView, он пытается сфокусировать первый возможный дочерний элемент в onRequestFocusInDescendants, и если только RecyclerView является фокусируемым, он выигрывает.

Изменить (спасибо Waran): и для плавной прокрутки не забудьте установить yourRecyclerView.setNestedScrollingEnabled(false);

person Dmitry Gavrilko    schedule 18.05.2016
comment
Кстати, вам нужно добавить yourRecyclerView.setNestedScrollingEnabled (false); чтобы сделать прокрутку плавной - person Waran-; 02.06.2016
comment
Работает отлично, но вы должны обязательно добавить указанный выше атрибут к представлениям, которые не отображаются после прокрутки. - person spectre10; 02.08.2016
comment
простите, а где мне добавить этот атрибут? ко всем моим представлениям, кроме Recycle View ?? - person Mahdi; 19.12.2016
comment
@Kenji только для первого просмотра внутри LinearLayout, где находится RecyclerView. Или к самому LinearLayout (контейнер RecyclerView), если первое изменение не помогает. - person Dmitry Gavrilko; 21.12.2016
comment
@DmitryGavrilko У меня проблема, когда RecycleView находится внутри NestedScrollview. Я не могу использовать recycleview.scrollToPosition (X); , это просто не работает. Я перепробовал все за последние 6 дней, но я могу это преодолеть. любое предложение? Буду очень благодарен! - person Karoly; 06.01.2017
comment
Вы также можете просто установить android:focusableInTouchMode="false" в recyclerView, чтобы вам не нужно было устанавливать true для всех других представлений. - person Aksiom; 07.01.2017
comment
Согласен с @Aksiom. Установка recyclerView.setFocusableInTouchMode (false) у меня хорошо сработала. - person whizzle; 07.03.2017
comment
Согласен с Дмитрием Гаврилко, работал после установки android: focusableInTouchMode = true для linearlayout, который содержит recyclerview. Настройка @Aksiom android: focusableInTouchMode = false для recyclerView не сработала для меня. - person Shendre Kiran; 14.07.2019

В вашем LinearLayout сразу после NestedScrollView используйте android:descendantFocusability следующим образом

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

ИЗМЕНИТЬ

Поскольку для многих из них этот ответ полезен, они также предоставят объяснения.

Использование descendantFocusability описано здесь. И с focusableInTouchMode через здесь. Таким образом, использование blocksDescendants в descendantFocusability не позволяет ребенку получать фокус при касании, и, следовательно, незапланированное поведение может быть остановлено.

Что касается focusInTouchMode, то и AbsListView, и RecyclerView по умолчанию вызывают метод setFocusableInTouchMode(true); в своем конструкторе, поэтому нет необходимости использовать этот атрибут в ваших макетах XML.

А для NestedScrollView используется следующий метод:

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

Здесь используется метод setFocusable() вместо setFocusableInTouchMode(). Но согласно этому сообщению, focusableInTouchMode следует избегать, кроме определенных условий, поскольку это нарушает согласованность с нормальным поведением Android. Игра - хороший пример приложения, которое может эффективно использовать свойство фокусируемого в сенсорном режиме. MapView, если он используется в полноэкранном режиме, как в Google Maps, является еще одним хорошим примером того, как правильно использовать фокусируемый в сенсорном режиме.

person Jimit Patel    schedule 07.11.2016
comment
Спасибо! В моем случае это работает. К сожалению, android: focusableInTouchMode = true у меня не сработал. - person Mikhail; 13.02.2017
comment
Это сработало для меня. Я добавил эту строку кода в родительский вид моего recyclerview (в моем случае это был LinearLayout), и он работал как шарм. - person amzer; 25.05.2017
comment
Я использовал NestedScrollView, а затем LinearLayout, который содержит RecyclerView и EditText. Поведение при прокрутке было исправлено с помощью android: DescendantFocusability = blocksDescendants внутри LinearLayout, но EditText не может получить фокус. Есть идеи, что происходит? - person Sagar Chapagain; 26.06.2017
comment
@SagarChapagain Это свойство blocksDescendants блокировать фокус всех потомков. Я не уверен, но попробуйте beforeDescendants - person Jimit Patel; 26.06.2017
comment
@JimitPatel моя проблема была решена с помощью recyclerView.setFocusable(false); nestedScrollView.requestFocus(); - person Sagar Chapagain; 26.06.2017
comment
Что делать, если у меня есть осязаемые представления в recyclerView? Свойство blocksDescendants предотвратит срабатывание триггеров прослушивателей кликов, не так ли? - person Leandro Temperoni; 29.04.2019
comment
@LeandroTemperoni На самом деле, тот, кто сомневался в этом, никогда не имел в своем коде элементов, связанных с фокусом, так что это решение. Подойдя к вашей проблеме, я думаю, что фокус для EditText - это проблема, но для Button даже я не знаю об этом, он должен работать по моему мнению, потому что я использовал горизонтальный RecyclerView с щелчком по элементу для перехода на следующий экран. Но с последними обновлениями я не уверен. Попробуйте !! и скажите, работает он или нет. :) - person Jimit Patel; 30.04.2019
comment
Это должен быть принятый ответ - person etomun; 06.01.2021
comment
Я испытал нечто подобное в макете с AppBarLayout + CollapsingToolbarLayout + NestedScrollView + RecyclerView. Добавление android:clipToPadding="true" к NestedScrollView и RecyclerView, плюс android:descendantFocusability="blocksDescendants" к дочерней группе просмотра NestedScrollView и android:nestedScrollingEnabled="false" к RecyclerView устранило все проблемы. - person David Miguel; 02.03.2021

android:descendantFocusability="blocksDescendants"

внутри LinearLayout работал у меня.

person Subash Bhattarai    schedule 15.03.2017
comment
Но означает ли это, что фокус на представлениях внутри будет заблокирован, и пользователи не смогут до них добраться? - person android developer; 10.08.2017

У меня была такая же проблема, и я решил расширить NestedScrollView и отключить фокусировку детей. По какой-то причине RecyclerView всегда запрашивал фокус, даже когда я просто открывал и закрывал ящик.

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;
}
}
person Lubos Horacek    schedule 25.04.2016
comment
Это работает, но также нарушает концепцию фокуса, и вы можете легко получить ситуацию с двумя сфокусированными полями на экране. Поэтому используйте его, только если у вас нет EditText внутри RecyclerView держателей. - person Andrey Chernoprudov; 11.03.2020

В моем случае этот код решает мою проблему

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

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

//populate recyclerview here

Мой макет содержит родительский макет как NestedScrollView, у которого есть дочерний LinearLayout. LinearLayout имеет "вертикальную" ориентацию и дочерние элементы RecyclerView и EditText. Справочник

person Sagar Chapagain    schedule 26.06.2017

Просто добавьте android:descendantFocusability="blocksDescendants" в ViewGroup внутри NestedScrollView.

person Gilbert Parreno    schedule 03.10.2019

У меня две догадки.

Во-первых: попробуйте поместить эту строку в свой NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Во-вторых: используйте

<android.support.design.widget.CoordinatorLayout

как ваш родительский взгляд.

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

My last possible solution. I swear :)

person Andre Haueisen    schedule 30.03.2016
comment
И добавление CoordinatorLayout в качестве родительского представления тоже не сработало ... В любом случае спасибо - person Luccas Correa; 30.03.2016

Эта проблема возникает из-за повторного использования View Focus.

Автоматически весь фокус переходит в режим повторного просмотра, если его размер увеличивал размер экрана.

добавление android:focusableInTouchMode="true" в первый ChildView, например TextView, Button и т. д. (Не в ViewGroup, например Linear, Relative и т. д.) имеет смысл для решения проблемы, но решение уровня API 25 и выше не работает.

Просто добавьте эти 2 строки в свой ChildView, например TextView, Button и так далее (не на ViewGroup, как Linear, Relative и так далее)

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

Я только что столкнулся с этой проблемой на уровне API 25. Надеюсь, другие люди не тратят на это время.

Для плавной прокрутки в RecycleView добавьте эту строку

 android:nestedScrollingEnabled="false"

но добавление этих атрибутов работает только с уровнем API 21 или выше. Если вы хотите, чтобы сглаживающая прокрутка работала ниже уровня API 25, добавьте эту строку в свой класс

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);
person sushildlh    schedule 12.12.2018

В коде Java после инициализации recyclerView и настройки адаптера добавьте эту строку:

recyclerView.setNestedScrollingEnabled(false)

Вы также можете попробовать обернуть макет с помощью relativeLayout, чтобы представления оставались в том же положении, но recyclerView (который прокручивается) первым в иерархии xml. Последнее предложение - отчаянная попытка: p

person johnny_crq    schedule 30.03.2016

Поскольку я опаздываю с ответом, но может помочь кому-то другому. Просто используйте версию ниже или выше на уровне вашего приложения build.gradle, и проблема будет устранена.

compile com.android.support:recyclerview-v7:23.2.1
person Amit    schedule 16.01.2017

чтобы прокрутить вверх, просто вызовите это в setcontentview:

scrollView.SmoothScrollTo(0, 0);
person user3844824    schedule 20.02.2017
comment
это не дает ответа на вопрос - person Umar Ata; 28.09.2018

Пожалуйста, не используйте

android:descendantFocusability="blocksDescendants"

потому что ViewGroup блокирует получение фокуса своими потомками. Это проблема доступности.

Если вы хотите избежать получения фокуса nestedscrollview, вы можете добавить фиктивный макет, который может быть сфокусирован над nestedscrollview.

   <View
       android:layout_width="0dp"
       android:layout_height="0dp"
       android:focusable="true"
       android:focusableInTouchMode="true" />
person Anju    schedule 04.03.2021

В моем случае это была прокрутка и фокусировка на recyclerview из-за того, что я добавил android: layout_gravity = center в родительский linearlayout. После снятия работает исправно.

person Harshit Jain    schedule 08.03.2021