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

130

Я получаю странное поведение при прокрутке, когда добавляю 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"/>
Луккас Корреа
источник
пробовал с android:focusableInTouchMode="false"for RecyclerView? что-то явно означает «заставляет ваш макет идти вниз ... (где он начинается, когда у вас 1295 элементов? снизу или просто небольшое верхнее смещение, как на первом экране)»
snachmsm
Установите android: clipToPadding = «true» в свой NestedScrollView.
natario
также: сохранение прокручиваемого вида внутри другого вида с такой же прокруткой - не очень хороший шаблон ... надеюсь, вы используете правильныйLayoutManager
snachmsm
Пробовал оба ваших предложения и, к сожалению, ни один из них не сработал. @snachmsm, независимо от количества элементов в представлении ресайклера, смещение всегда одно и то же. Что касается того, является ли размещение RecyclerView внутри NestedScrollView хорошим шаблоном, это на самом деле было рекомендовано инженером Google по адресу plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa
1
Даже у меня такая же проблема при использовании RecyclerView внутри NestedScrollView. Это плохой шаблон, потому что сам шаблон ресайклера не работает. Все представления будут отрисованы одновременно (поскольку WRAP_CONTENT требует высоты представления ресайклера). В фоновом режиме не будет повторного использования представления, поэтому основная цель самого представления recycler не работает. Но с помощью режима ресайклера легко управлять данными и рисовать макет, это единственная причина, по которой вы можете использовать этот шаблон. Так что лучше не использовать его до тех пор, пока он вам точно не понадобится.
Ашок Варма

Ответы:

254

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

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

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

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

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

Дмитрий Гаврилко
источник
12
Кстати, вам нужно добавить yourRecyclerView.setNestedScrollingEnabled (false); чтобы сделать прокрутку гладкой
Waran
1
@Kenji только для первого просмотра внутри LinearLayout, где находится RecyclerView. Или к самому LinearLayout (контейнер RecyclerView), если первое изменение не помогает.
Дмитрий Гаврилко
1
@DmitryGavrilko У меня проблема, когда RecycleView находится внутри NestedScrollview. Я не могу использовать recycleview.scrollToPosition (X); , это просто не работает. Я перепробовал все за последние 6 дней, но я могу это преодолеть. любое предложение? Буду очень признателен!
Karoly
3
Вы также можете просто установить android:focusableInTouchMode="false"recyclerView, чтобы вам не нужно было устанавливать true для всех других представлений.
Aksiom
1
Согласен с Дмитрием Гаврилко, работал после установки android: focusableInTouchMode = "true" в linearlayout, который содержит recyclerview. Настройка @Aksiom android: focusableInTouchMode = "false" для recyclerView у меня не работает.
Shendre Kiran
103

В вашем 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 do не позволяет ребенку сосредоточиться при прикосновении, и, следовательно, можно остановить незапланированное поведение.

Что касается 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, является еще одним хорошим примером того, как правильно использовать фокусируемое в сенсорном режиме.

Джимит Патель
источник
Спасибо! В моем случае это работает. К сожалению, android: focusableInTouchMode = "true" у меня не сработал.
Михаил
1
Это сработало для меня. Я добавил эту строку кода в родительский вид моего recyclerview (в моем случае это был LinearLayout), и он работал как шарм.
amzer
Я использовал NestedScrollView, а затем LinearLayout, который содержит RecyclerView и EditText. Поведение при прокрутке было исправлено с помощью android: DescendantFocusability = "blocksDescendants" внутри LinearLayout, но EditText не может получить фокус. Есть идеи, что происходит?
Sagar Chapagain
@SagarChapagain Это свойство blocksDescendantsблокировать фокус всех потомков. Я не уверен, но попробуйтеbeforeDescendants
Джимит Патель
2
@JimitPatel, моя проблема была решена с помощьюrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Сагар Чапагейн
17
android:descendantFocusability="blocksDescendants"

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

Субаш Бхаттарай
источник
6
Но означает ли это, что фокус на представлениях внутри будет заблокирован, и пользователи не смогут до них добраться?
разработчик Android
11

У меня была такая же проблема, и я решил расширить 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;
}
}
Любош Горачек
источник
Это работает, но также нарушает концепцию фокуса, и вы можете легко получить ситуацию с двумя сфокусированными полями на экране. Поэтому используйте его, только если у вас нет EditText внутри RecyclerViewдержателей.
Андрей Чернопрудов
4

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

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. Ссылка

Сагар Чапагейн
источник
2

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

Во-первых: попробуйте поместить эту строку в свой 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>

Мое последнее возможное решение. Клянусь :)

Андре Хауэйзен
источник
И добавление CoordinatorLayout в качестве родительского представления тоже не сработало ... В любом случае спасибо
Луккас Корреа
2

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

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

добавление android:focusableInTouchMode="true"к первому ChildView как TextView, Buttonи так далее (не на ViewGroupкак Linear, Relativeи так далее) имеет смысл , чтобы решить эту проблему , но API Level 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);
sushildlh
источник
0

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

recyclerView.setNestedScrollingEnabled(false)

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

johnny_crq
источник
0

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

compile com.android.support:recyclerview-v7:23.2.1
Amit
источник
0

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

scrollView.SmoothScrollTo(0, 0);
user3844824
источник
это не дает ответа на вопрос
Умар Ата
0

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

Гилберт Паррено
источник