Подтвердить что ты не робот

Как избежать блокировки самой прокрутки при использовании setNestedScrollingEnabled (false)?

Фон

У нас довольно сложная компоновка, в которой есть CollapsingToolbarLayout, вместе с RecyclerView внизу.

В некоторых случаях мы временно отключим расширение/сбой CollapsingToolbarLayout, вызвав setNestedScrollingEnabled (boolean) в RecyclerView.

Проблема

Обычно это нормально.

Однако, в некоторых (бит редких) случаях медленная прокрутка в RecyclerView получает полублокировку, то есть она пытается прокрутить назад при прокрутке вниз. Как будто он имеет 2 прокрутки, которые сражаются друг с другом (прокрутите вверх и прокрутите вниз):

введите описание изображения здесь

Код для запуска этого типа таков:

Рез/макет/activity_scrolling.xml

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/nestedView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            android:id="@+id/disableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="disable"/>

        <Button
            android:id="@+id/enableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="enable"
            />
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

ScrollingActivity.java

public class ScrollingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final RecyclerView nestedView = (RecyclerView) findViewById(R.id.nestedView);
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(false);
            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(true);
            }
        });
        nestedView.setLayoutManager(new LinearLayoutManager(this));
        nestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

}

Что я пробовал

Сначала я подумал об этом из-за чего-то другого (я подумал, что это странная комбинация с DrawerLayout), но затем я нашел минимальный образец, чтобы показать его, и он так же, как я думал: все это из-за setNestedScrollingEnabled.

Я попытался сообщить об этом на веб-сайте Google ( здесь), надеясь, что он будет исправлен, если он реальный ошибка. Если вы хотите попробовать или посмотреть видео с проблемой, перейдите туда, так как я не могу загрузить их все здесь (слишком большое и слишком много файлов).

Я также пытался использовать специальные флаги, как указано в других сообщениях (примеры: здесь, здесь, здесь, здесь и здесь), но никто не помог. На самом деле у каждого из них была проблема, будь она в расширенном режиме или прокрутка по-другому, чем то, что я делаю.

Вопросы

  • Это известная проблема? Почему это происходит?

  • Есть ли способ преодолеть это?

  • Есть ли альтернатива для вызова этой функции setNestedScrollingEnabled? Один без каких-либо проблем с прокруткой или блокировкой состояния CollapsingToolbarLayout?

4b9b3361

Ответ 1

Это альтернативный подход к достижению той же цели, что и этот ответ. Хотя этот ответ использовал Reflection, этот ответ не существует, но рассуждения остаются теми же.

Почему это происходит?

Проблема заключается в том, что RecyclerView иногда использует устаревшее значение для переменной-члена mScrollOffset. mScrollOffset устанавливается только в двух местах в RecyclerView: dispatchNestedPreScroll и dispatchNestedScroll. Мы имеем дело только с dispatchNestedPreScroll. Этот метод вызывается RecyclerView#onTouchEvent, когда он обрабатывает события MotionEvent.ACTION_MOVE.

Ниже приведена документация для dispatchNestedPreScroll.

dispatchNestedPreScroll

boolean dispatchNestedPreScroll (int dx,                 int dy,                 int [] потребляется,                 int [] offsetInWindow)

Отправляйте один шаг вложенного прокрутки, пока это представление не поглотит какую-либо его часть.

Вложенные события предварительной прокрутки - это вложенные события прокрутки, которые сенсорный перехват должен касаться. dispatchNestedPreScroll дает возможность родительскому представлению во вложенной операции прокрутки потреблять некоторые или все операции прокрутки до того, как детское представление потребляет его.

...

offsetInWindow int: Необязательно. Если не null, то при возврате это будет содержать смещение в локальных координатах представления этого представления до этой операции до завершения. Представления представления могут использовать это для настройки ожидаемого отслеживания входных координат.

offsetInWindow на самом деле является int[2] со вторым индексом, представляющим сдвиг y, который должен применяться к RecyclerView из-за вложенной прокрутки.

RecyclerView#DispatchNestedPrescroll разрешает метод с тем же именем в NestedScrollingChildHelper.

Когда RecyclerView вызывает dispatchNestedPreScroll, mScrollOffset используется как аргумент offsetInWindow. Поэтому любые изменения, внесенные в offsetInWindow, напрямую обновляются mScrollOffset. dispatchNestedPreScroll обновляет mScrollOffset , пока действует вложенная прокрутка. Если вложенная прокрутка не действует, то mScrollOffset не обновляется, а переходит со значением, которое было последним установлено dispatchNestedPreScroll. Таким образом, когда вложенная прокрутка отключена, значение mScrollOffset становится сразу же устаревшим, но RecyclerView продолжает использовать его.

Правильное значение mScrollOffset[1] при возврате из dispatchNestedPreScroll - это величина, которую нужно настроить для input coordinate tracking (см. выше). В RecyclerView следующие строки регулируют координату y touch:

mLastTouchY = y - mScrollOffset[1];

Если mScrollOffset[1], скажем, -30 (потому что он устарел и должен быть равен нулю), то mLastTouchY будет выключен на +30 пикселей (-30 = + 30). Эффект этого просчета состоит в том, что будет казаться, что прикосновение произошло дальше по экрану, чем это было на самом деле. Таким образом, медленная прокрутка вниз будет прокручиваться вверх, и вверх прокрутка будет прокручиваться быстрее. (Если прокрутка вниз достаточно быстро, чтобы преодолеть этот барьер 30px, тогда прокрутка вниз будет происходить, но медленнее, чем она должна.) Прокрутка вверх будет слишком быстрой, так как приложение считает, что больше места было покрыто.

mScrollOffset будет продолжаться как устаревшая переменная до тех пор, пока вложенная прокрутка не будет включена, а dispatchNestedPreScroll снова сообщит правильное значение в mScrollOffset.

Подход

Так как mScrollOffset[1] имеет устаревшее значение при определенных обстоятельствах, цель состоит в том, чтобы установить его в правильном значении при этих обстоятельствах. Это значение должно быть равно нулю, когда вложенная прокрутка не выполняется, то есть когда AppBar расширяется или сворачивается. К сожалению, mScrollOffset является локальным для RecyclerView, и для него нет установщика. Чтобы получить доступ к mScrollOffset, не прибегая к Reflection, создается пользовательский RecyclerView, который переопределяет dispatchNestedPreScroll. Четвертое приложение offsetInWindow, которое является переменной, которую мы должны изменить.

Простой mScrollOffset возникает, когда вложенная прокрутка отключена для RecyclerView. Дополнительное условие, которое мы будем налагать, заключается в том, что AppBar должен быть бездействующим, поэтому мы можем с уверенностью сказать, что mScrollOffset[1] должен быть равен нулю. Это не проблема, поскольку CollapsingToolbarLayout указывает snap в флажках прокрутки.

В примере приложения ScrollingActivity был изменен для записи, когда AppBar был расширен и закрыт. Также был создан обратный вызов (clampPrescrollOffsetListener), который вернет true, когда будут выполнены наши два условия. Наш переопределенный dispatchNestedPreScroll вызовет этот обратный вызов и зажим mScrollOffset[1] к нулю в ответе true.

Обновленный исходный файл для ScrollingActivity представлен ниже, как и пользовательский RecyclerView - MyRecyclerView. Файл макета XML должен быть изменен, чтобы отобразить пользовательский MyRecyclerView.

ScrollingActivity

public class ScrollingActivity extends AppCompatActivity
        implements MyRecyclerView.OnClampPrescrollOffsetListener {

    private CollapsingToolbarLayout mCollapsingToolbarLayout;
    private AppBarLayout mAppBarLayout;
    private MyRecyclerView mNestedView;
    // This variable will be true when the app bar is completely open or completely collapsed.
    private boolean mAppBarIdle = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mNestedView = (MyRecyclerView) findViewById(R.id.nestedView);
        mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
        mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);

        // Set the listener for the patch code.
        mNestedView.setOnClampPrescrollOffsetListener(this);

        // Listener to determine when the app bar is collapsed or fully open (idle).
        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                mAppBarIdle = verticalOffset == 0
                        || verticalOffset <= appBarLayout.getTotalScrollRange();
            }
        });
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // If the AppBar is fully expanded or fully collapsed (idle), then disable
                // expansion and apply the patch; otherwise, set a flag to disable the expansion
                // and apply the patch when the AppBar is idle.
                setExpandEnabled(false);

            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                setExpandEnabled(true);
            }
        });
        mNestedView.setLayoutManager(new LinearLayoutManager(this));
        mNestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

    private void setExpandEnabled(boolean enabled) {
        mNestedView.setNestedScrollingEnabled(enabled);
    }

    // Return "true" when the app bar is idle and nested scrolling is disabled. This is a signal
    // to the custom RecyclerView to clamp the y prescroll offset to zero.
    @Override
    public boolean clampPrescrollOffsetListener() {
        return mAppBarIdle && !mNestedView.isNestedScrollingEnabled();
    }

    private static final String TAG = "ScrollingActivity";
}

MyRecyclerView

public class MyRecyclerView extends RecyclerView {
    private OnClampPrescrollOffsetListener mPatchListener;

    public MyRecyclerView(Context context) {
        super(context);
    }

    public MyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // Just a call to super plus code to force offsetInWindow[1] to zero if the patchlistener
    // instructs it.
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        boolean returnValue;
        int currentOffset;
        returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        currentOffset = offsetInWindow[1];
        Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset);
        if (mPatchListener.clampPrescrollOffsetListener() && offsetInWindow[1] != 0) {
            Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset + " -> 0");
            offsetInWindow[1] = 0;
        }
        return returnValue;
    }

    public void setOnClampPrescrollOffsetListener(OnClampPrescrollOffsetListener patchListener) {
        mPatchListener = patchListener;
    }

    public interface OnClampPrescrollOffsetListener {
        boolean clampPrescrollOffsetListener();
    }

    private static final String TAG = "MyRecyclerView";
}

Ответ 2

Собственно, вы можете неправильно рассмотреть проблему.

Единственное, что вам нужно - это установить флаги Toolbar. Вы ничего не делаете, поэтому я бы сказал, что ваш макет должен быть упрощен до:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
         android:id="@+id/app_bar"
         android:layout_width="match_parent"
         android:layout_height="@dimen/app_bar_height"
         android:fitsSystemWindows="true"
         android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:title="Title" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/nestedView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"            
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            android:id="@+id/disableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="disable"/>

        <Button
            android:id="@+id/enableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="enable"
            />
    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

Затем, когда вы хотите отключить свертывание, просто установите флаги панели инструментов:

// To disable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
toolbar.setLayoutParams(params);

И чтобы включить

// To enable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL|AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
toolbar.setLayoutParams(params);

Удерживайте ссылку на параметры макета, если вы меняете, а не получаете его все время.

Если вам нужно CollapsingToolbarLayout получить и установить LayoutParams на View, обновите флаги одинаково, но теперь добавление appBarLayout.setExpanded(true/false)

Примечание. Использование setScrollFlags очищает все предыдущие флаги, поэтому будьте осторожны и установите все необходимые флаги при использовании этого метода.

Ответ 3

Как указывает @Moinkhan, вы можете попробовать обернуть RecyclerView и следующие элементы в NestedScrollView, как это, это должно решить вашу проблему прокрутки вместе с вашим сворачивающимся расположением панели инструментов:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="fill_vertical"
        android:fillViewport="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/nestedView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

        </RelativeLayout>

    </android.support.v4.widget.NestedScrollView>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            android:id="@+id/disableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="disable"/>

        <Button
            android:id="@+id/enableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="enable"
            />
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

Если содержимое recyclerview не отображается, вы можете следовать этой теме, чтобы решить эту проблему Как использовать RecyclerView внутри NestedScrollView?.

Надеюсь, что это поможет.

Ответ 4

в представлении ресайклера, для прокрутки плавного

android:nestedScrollingEnabled="false" 

для перекрытия картыView на панели инструментов

 app:behavior_overlapTop = "24dp" 

Попробуйте этот код для CollapsingToolbar:

  <android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:title="Title" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>


    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:background="@android:color/transparent"
        app:behavior_overlapTop="@dimen/behavior_overlap_top"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:id="@+id/linearLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_min_padding"
                android:nestedScrollingEnabled="false"
                android:scrollbarSize="2dp"
                android:scrollbarStyle="outsideInset"
                android:scrollbarThumbVertical="@color/colorAccent"
                android:scrollbars="vertical" />

        </LinearLayout>

    </android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

Снимок экрана

Ответ 5

Используйте следующий код, он отлично работает для меня:

lockAppBarClosed();
ViewCompat.setNestedScrollingEnabled(recyclerView, false);   // to lock the CollapsingToolbarLayout

и реализуем следующие методы:

private void setAppBarDragging(final boolean isEnabled) {
        CoordinatorLayout.LayoutParams params =
                (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
        AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
        behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
            @Override
            public boolean canDrag(AppBarLayout appBarLayout) {
                return isEnabled;
            }
        });
        params.setBehavior(behavior);
    }

    public void unlockAppBarOpen() {
        appBarLayout.setExpanded(true, false);
        appBarLayout.setActivated(true);
        setAppBarDragging(false);
    }

    public void lockAppBarClosed() {
        appBarLayout.setExpanded(false, false);
        appBarLayout.setActivated(false);
        setAppBarDragging(false);

    }

Ответ 6

Я полагаю, что эта проблема связана с разворачиванием сворачивающейся панели инструментов (закрытой или открытой) и оставлением переменной сдвига по вертикали (mScrollOffset[1] in RecyclerView) с ненулевым значением, которое впоследствии смещает прокрутку - замедляя или изменяя свиток в одном направлении и ускоряя его в другом. Кажется, что эта переменная установлена ​​в NestedScrollingChildHelper, если включена вложенная прокрутка. Таким образом, любое значение mScrollOffset[1] остается неизменным после отключения прокрутки гнезда.

Чтобы надежно воспроизвести эту проблему, вы можете заставить панель инструментов встать на место, а затем сразу же отключить ее. См. это видео для демонстрации. Я полагаю, что величина вопроса зависит от того, сколько происходит "привязки".

Если я перетащил панель инструментов в полностью открытое или закрытое положение и не позволяю ей "привязывать", тогда я не смог воспроизвести эту проблему, а mScrollOffset[1] установлен на ноль, который, по моему мнению, является правильным значением, Я также воспроизвел проблему, удалив snap из layout_scrollFlags сбрасывающейся панели инструментов в макете и помещая панель инструментов в частично открытое состояние.

Если вы хотите поиграть с этим, вы можете поместить свое демо-приложение в режим отладки и наблюдать за значением mScrollOffset[1] в RecyclerView#onTouchEvent. Также рассмотрите методы NestedScrollingChildHelper dispatchNestedScroll и dispatchNestedPreScroll, чтобы увидеть, как смещение устанавливается только при включении вложенной прокрутки.

Итак, как это исправить? mScrollOffset является закрытым для RecyclerView, и не сразу видно, как подклассировать что-либо, чтобы изменить значение mScrollOffset[1]. Это оставило бы отражение, но это может быть нежелательно для вас. Возможно, у другого читателя есть представление о том, как подойти к этому или узнать какой-то секретный соус. Я буду пересказывать, если что-нибудь случится со мной.

Изменить: Я предоставил новый класс ScrollingActivity.java, который преодолеет эту проблему. Он использует отражение и применяет патч для установки mScrollOffset[1] of RecyclerView на ноль, когда нажата кнопка отключения прокрутки, и AppBar простаивает. Я провел предварительное тестирование, и он работает. Вот gist. (См. обновленный gist ниже.)

Второе редактирование: Я смог заставить панель инструментов смеяться и застрять посередине без патча, так что это не похоже на то, что патч вызывает определенную проблему. Я могу заставить панель инструментов отскакивать от полностью открытой, чтобы рухнуть, прокручивая ее достаточно быстро в нерасположенном приложении.

Я еще раз посмотрел, что делает патч, и я думаю, что он будет себя вести: переменная является частной и упоминается только в одном месте после выключения прокрутки. При включенной прокрутке переменная всегда reset перед использованием. Настоящий ответ для Google заключается в устранении этой проблемы. Пока они этого не сделают, я думаю, что это может быть самым близким к тому, что вы можете достичь приемлемой работы с этим конкретным дизайном. (Я опубликовал обновленный gist, который устраняет потенциальные проблемы с быстрым щелчком, оставляя переключатели в потенциальном неподходящем состоянии.)

Независимо, основная проблема была выявлена, и у вас есть надежный способ воспроизвести проблему, поэтому вы можете более легко проверить другие предлагаемые решения.

Надеюсь, это поможет.