Перегляд переробника всередині NestedScrollView викликає запуск прокрутки в середині


129

Я отримую дивну поведінку прокрутки, коли додаю RecyclerView всередині NestedScrollView.

Що трапляється, що коли перегляд прокрутки має більше рядків, ніж може бути показано на екрані, як тільки активність запущена, NestedScrollView починається зі зміщення вгорі (зображення 1). Якщо у вікні прокрутки є декілька предметів, щоб їх можна було відобразити одразу, цього не відбувається (зображення 2).

Я використовую версію 23.2.0 бібліотеки підтримки.

Зображення 1 : WRONG - починається зі зміщення зверху

Зображення 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

Працює правильно, якщо я розміщую такий код при ініціалізації своєї діяльності:

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"за RecyclerView? smth чітко "змушує ваш макет перейти до низу ... (з чого він починається, коли у вас є 1295 елементів? знизу або просто невеликий верхній зсув, як перший екран)
snachmsm

Встановіть android: clipToPadding = "вірно" для вашого NestedScrollView.
Натаріо

також: збереження прокручуваного виду всередині іншого виду, який можна прокручувати, не дуже вдалий зразок ... сподіваюся, що ви користуєтесь належним чиномLayoutManager
snachmsm

Випробували обидві ваші пропозиції та, на жаль, не працювали. @snachmsm незалежно від кількості елементів у перегляді переробника, зсув завжди однаковий. Що стосується того, чи є розміщення RecyclerView всередині NestedScrollView хорошим зразком, це фактично рекомендував інженер Google за адресою plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa

1
Навіть у мене така ж проблема при використанні RecyclerView всередині NestedScrollView. Це погана картина, оскільки сама модель рециркулятора не працюватиме. Усі перегляди будуть складені одночасно (оскільки WRAP_CONTENT потребує висоти перегляду утилізатора). У фоновому режимі не буде переробки будь-якого виду, тому головне призначення подання переробника не працює. Але керувати даними та малювати макет легко, використовуючи перегляд рециркулятора, легко. Це єдина причина, коли ви можете використовувати цю модель. Тож краще не користуватися ним, доки ви не вимагаєте цього точно.
Ашок Варма

Відповіді:


253

Я вирішив таке питання, встановивши:

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

на мій погляд над RecyclerView (який було приховано після небажаної прокрутки). Спробуйте встановити цю властивість у LinearLayout над RecyclerView або LinearLayout, який є контейнером RecyclerView (допоміг мені в іншому випадку).

Як я бачу в джерелі NestedScrollView, він намагається зосередити першого можливого дитини в OnRequestFocusInDescendants, і якщо тільки RecyclerView є орієнтованим, він виграє.

Редагувати (завдяки Варану): і для плавного прокручування не забудьте встановити yourRecyclerView.setNestedScrollingEnabled(false);


12
До речі, вам потрібно додати yourRecyclerView.setNestedScrollingEnabled (помилково); щоб зробити прокрутку гладкою
Варан

1
@Kenji лише для першого перегляду всередині LinearLayout, де розміщено RecyclerView. Або до самого LinearLayout (контейнера RecyclerView), якщо перша зміна не допоможе.
Дмитро Гаврилко

1
@DmitryGavrilko У мене виникла проблема, коли RecycleView знаходиться всередині NestedScrollview. Я не можу використовувати recycleview.scrollToPosition (X); , це просто не працює. Я спробував все за останні 6 днів, але можу перебороти це. будь-яка пропозиція? Я був би дуже вдячний!
Каролі

3
Ви також можете просто встановити android:focusableInTouchMode="false"на recilerView, щоб вам не потрібно було встановити значення true для всіх інших представлень.
Аксіома

1
Погодьтеся з Дмитром Гаврилко, який працював після встановлення андроїда: focusableInTouchMode = "вірно" для лінійного розкладу, який містить recilerlerview. @Aksiom налаштування android: focusableInTouchMode = "false" для recilerView не працює для мене.
Шендре Кіран

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

EDIT

Оскільки багато з них отримують цю відповідь корисною, також нададуть пояснення.

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


Дякую! Це працює в моєму випадку. На жаль android: focusableInTouchMode = "true" не працює для мене.
Михайло

1
Це працювало для мене. Я додав цей рядок коду до батьківського виду мого рециркулятора (у моєму випадку це був LinearLayout), і він працював як шарм.
amzer

Я використовував NestedScrollView, а потім LinearLayout, який містить RecyclerView та EditText. Поведінка прокрутки була виправлена ​​за допомогою android: descendantFocusability = "блокиDescendants" всередині LinearLayout, але EditText не може отримати фокус. Будь-яка ідея, що відбувається?
Sagar Chapagain

@SagarChapagain Це властивість blocksDescendantsблокувати фокус усіх нащадків. Я не впевнений, але спробуйтеbeforeDescendants
Jimit Patel

2
@JimitPatel мою проблему вирішили за допомогоюrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain

16
android:descendantFocusability="blocksDescendants"

всередині LinearLayout працював для мене.


6
Але чи означає це, що фокус на поглядах усередині буде заблокований, і користувачі не зможуть дійти до них?
андроїд розробник

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 або вище. Якщо ви хочете, щоб згладжування прокрутки працювало нижче рівня 25 API, тоді додайте цей рядок у своєму класі

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

0

У Java-коді після ініціалізації рециркулятораView та встановлення адаптера додайте цей рядок:

recyclerView.setNestedScrollingEnabled(false)

Ви також можете спробувати обернути макет за допомогою відносного макета, щоб представлення даних залишалися на одній і тій же позиції, але recilerView (який прокрутка) спочатку в ієрархії xml. Остання пропозиція відчайдушної спроби: с


0

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

compile com.android.support:recyclerview-v7:23.2.1

0

щоб прокрутити до вершини, просто зателефонуйте цьому setcontentview:

scrollView.SmoothScrollTo(0, 0);

це не дає відповіді на запитання
Умар Ата

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.