使用setNestedScrollingEnabled(false)时如何避免阻止滚动?

时间:2017-06-11 14:28:42

标签: android android-recyclerview android-collapsingtoolbarlayout

背景

我们有一个非常复杂的布局,其中包含CollapsingToolbarLayout,底部还有一个RecyclerView。

在某些情况下,我们通过在RecyclerView上调用setNestedScrollingEnabled(boolean)暂时禁用CollapsingToolbarLayout的展开/折叠。

问题

这通常可行。

但是,在某些(有点罕见)情况下,RecyclerView上的慢速滚动会被半阻止,这意味着它会在向下滚动时尝试向后滚动。就好像它有2个滚动相互争斗(向上滚动并向下滚动):

enter image description here

触发此操作的代码如下:

RES /布局/ 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的网站(here)上报告此问题,希望如果这是一个真正的错误,它将得到修复。如果你想尝试一下,或观看问题的视频,那就去那里,因为我不能在这里全部上传(太大和太多的文件)。

我也尝试按照其他帖子的说明使用特殊标记(示例:hereherehereherehere) ,但没有帮助。事实上,他们每个人都有一个问题,无论是停留在扩展模式,还是以与我不同的方式滚动。

问题

  1. 这是一个已知问题吗?为什么会这样?

  2. 有没有办法克服这个问题?

  3. 是否有一种替代方法可以调用setNestedScrollingEnabled的这个函数?一个没有任何滚动或锁定CollapsingToolbarLayout状态的问题?

8 个答案:

答案 0 :(得分:4)

实际上,您可能会以错误的方式查看问题。

您唯一需要的是相应地设置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至{{1}相反,以相同的方式更新标志,但现在添加View

注意:使用appBarLayout.setExpanded(true/false)会清除所有以前的标记,因此在使用此方法时请小心并设置所有必需的标记。

答案 1 :(得分:3)

正如@Moinkhan指出的那样,你可以尝试在这样的NestedScrollView中包装RecyclerView和下一个元素,这可以解决你在折叠工具栏布局中滚动的问题:

<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的内容,您可以按照此主题解决该问题How to use RecyclerView inside NestedScrollView?

希望它有所帮助。

答案 2 :(得分:3)

在回收站视图中,滚动顺畅

android:nestedScrollingEnabled="false" 

重叠工具栏中的cardView

 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>

Screenshot

答案 3 :(得分:2)

使用以下代码,它适用于我:

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

    }

答案 4 :(得分:2)

这是实现与this answer相同目标的替代方法。虽然这个答案使用了Reflection,但这个答案却没有,但推理仍然是一样的。

为什么会这样?

问题是RecyclerView有时会使用成员变量mScrollOffset的陈旧值。 mScrollOffset仅在RecyclerView中设置了dispatchNestedPreScrolldispatchNestedScrolldispatchNestedPreScroll。我们只关注RecyclerView#onTouchEvent。 {I}处理MotionEvent.ACTION_MOVE事件时,offsetInWindow会调用此方法。

以下内容来自dispatchNestedPreScroll的文档。

  

<强> dispatchNestedPreScroll

     

boolean dispatchNestedPreScroll(int dx,                   int dy,                   int []消耗了,                   int [] offsetInWindow)

     

在此视图消耗其中的任何部分之前,发出嵌套滚动的一个步骤。

     

嵌套预滚动事件是嵌套滚动事件触摸拦截要触摸的内容。 dispatchNestedPreScroll为嵌套滚动操作中的父视图提供了在子视图使用之前使用部分或全部滚动操作的机会。

     

...

     

offsetInWindow int:可选。如果不为null,则返回时将包含此视图在此操作之前到完成之后的本地视图坐标中的偏移量。 View实现可以使用它来调整预期的输入坐标跟踪。

int[2]实际上是一个RecyclerView,第二个索引表示由于嵌套滚动而应用于RecyclerView#DispatchNestedPrescroll的y移位。

RecyclerView解析为NestedScrollingChildHelper中具有相同名称的方法。

dispatchNestedPreScroll拨打mScrollOffset时, offsetInWindow用作offsetInWindow参数。因此,对mScrollOffset所做的任何更改都会直接更新dispatchNestedPreScroll。只要嵌套滚动生效<{i> ,mScrollOffset就会更新mScrollOffset 。如果嵌套滚动无效,则dispatchNestedPreScroll不会更新, 会继续使用上次由 mScrollOffset设置的值。因此,当关闭嵌套滚动时,RecyclerView的值立即变为陈旧但mScrollOffset[1]继续使用它。

dispatchNestedPreScroll返回时input coordinate tracking的正确值是要为RecyclerView调整的金额(见上文)。在mLastTouchY = y - mScrollOffset[1]; 中,以下行调整y触摸坐标:

mScrollOffset[1]

如果mLastTouchY是,请说 - -30(因为它已过时且应该为零),那么30px将关闭+30像素( - 30 = + 30) )。这种误算的结果是看起来触摸发生在屏幕下方比实际发生的更远。因此,慢速向下滚动实际上会向上滚动,向上滚动会滚动得更快。 (如果向下滚动足够快以克服此mScrollOffset障碍,则向下滚动将发生但比它应该更慢。)向上滚动将过快,因为应用程序认为已覆盖更多空间。

dispatchNestedPreScroll将继续作为陈旧变量,直到打开嵌套滚动并且mScrollOffset再次在mScrollOffset[1]中报告正确的值。

<强>方法

由于mScrollOffset在某些情况下具有陈旧价值,因此目标是在这些情况下将其设置为正确的值。当没有进行嵌套滚动时,即,当AppBar展开或折叠时,该值应该为零。不幸的是,RecyclerViewmScrollOffset的本地,并且没有设置器。要在不诉诸反射的情况下访问RecyclerView,我们会创建一个覆盖dispatchNestedPreScroll的自定义offsetInWindow。第四个文章是mScrollOffset,这是我们需要改变的变量。

只要RecyclerView禁用嵌套滚动,就会出现陈旧mScrollOffset[1]。我们将要施加的另一个条件是AppBar必须处于空闲状态,因此我们可以安全地说CollapsingToolbarLayout应为零。这不是问题,因为snap在滚动标记中指定了ScrollingActivity

在示例应用中,clampPrescrollOffsetListener已被修改为记录AppBar展开和关闭的时间。还创建了一个回调(true),当满足我们的两个条件时,它将返回dispatchNestedPreScroll。我们覆盖的mScrollOffset[1]会调用此回调,并在true响应时将ScrollingActivity限制为零。

RecyclerView - MyRecyclerView的更新源文件如下所示,自定义MyRecyclerView也是如此。 必须更改XML布局文件以反映自定义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"; }

<强> ScrollingActivity

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

<强> MyRecyclerView

{{1}}

答案 5 :(得分:2)

我必须解决一个类似的问题,并使用AppBarLayout上的自定义行为来完成。一切正常。 通过在自定义行为中覆盖onStartNestedScroll,可以阻止折叠的工具栏布局扩展或折叠,同时在我的情况下保持滚动视图(NestedScrollView)正常工作。我已经详细说明了here,希望对您有所帮助。

private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
    var canDrag = true
    var acceptsNestedScroll = true

    init {
        setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
            override fun canDrag(appBarLayout: AppBarLayout): Boolean {
                // Allow/Do not allow dragging down/up to expand/collapse the layout
                return canDrag
            }
        })
    }

    override fun onStartNestedScroll(parent: CoordinatorLayout,
                                     child: AppBarLayout,
                                     directTargetChild: View,
                                     target: View,
                                     nestedScrollAxes: Int,
                                     type: Int): Boolean {
        // Refuse/Accept any nested scroll event
        return acceptsNestedScroll
    }}

答案 6 :(得分:1)

我认为此问题与折叠工具栏捕捉到位(关闭或打开)并在mScrollOffset[1]中保留垂直偏移变量(RecyclerView)时具有非零值有关偏向滚动 - 在一个方向上减慢或反转滚动并在另一个方向上加速滚动。如果启用了嵌套滚动,则此变量似乎仅在NestedScrollingChildHelper中设置。因此,一旦禁用嵌套滚动,任何值mScrollOffset[1]都会保持不变。

要可靠地重现此问题,您可以使工具栏捕捉到位,然后立即单击禁用。有关演示,请参阅this video。我相信,问题的严重程度取决于多少&#34; snpping&#34;发生。

如果我将工具栏拖到完全打开或关闭的位置并且不要让它“快速”,那么我就无法重现此问题并设置了mScrollOffset[1]为零,我认为是正确的价值。我还通过从布局中折叠工具栏的snap中删除layout_scrollFlags并将工具栏置于部分打开状态来重现此问题。

如果您想玩这个,可以将您的演示应用程序置于调试模式,并观察mScrollOffset[1]RecyclerView#onTouchEvent的值。另请查看NestedScrollingChildHelper的{​​{1}}和dispatchNestedScroll方法,了解仅在启用嵌套滚动时如何设置偏移量。

那么,如何解决这个问题呢? dispatchNestedPreScrollmScrollOffset是私有的,并且如何对任何内容进行子类化以更改RecyclerView的值并不是显而易见的。这会留下反思,但这可能不适合你。也许另一位读者对如何处理这个或者知道一些秘密酱有一个想法。 如果发生任何事情,我会重新发布。

编辑:我提供了一个新的mScrollOffset[1]课程来克服这个问题。当按下禁用滚动按钮且AppBar空闲时,它确实使用反射并应用补丁将ScrollingActivity.java的{​​{1}}设置为零。我做了一些初步测试,它正在运行。 以下是gist(请参阅下面的更新要点。)

第二次修改:我能够让工具栏以有趣的方式捕捉并在没有补丁的情况下卡在中间,所以看起来修补程序看起来并不是特定的问题。通过在未修补的应用程序中快速向下滚动,我可以让工具栏从完全打开弹回到折叠状态。

我还看了一下补丁正在做什么,我认为它会表现自己:变量是私有的,只在滚动关闭后才在一个地方引用。启用滚动后,变量将始终在使用前重置。真正的答案是谷歌解决这个问题。在他们这样做之前,我认为这可能是您可以接受这种特殊设计的最接近的解决方案。 (我发布了一个updated gist,可以解决潜在的问题,快速点击,让交换机处于潜在的不合适状态。)

无论如何,已经确定了潜在的问题,并且您有可靠的方法来重现问题,因此您可以更轻松地验证其他建议的解决方案。

我希望这会有所帮助。

答案 7 :(得分:0)

我想提出一个不错的选择,主要基于一个here

AppBarLayoutEx.kt

class AppBarLayoutEx : AppBarLayout {
    private var isAppBarExpanded = true
    private val behavior = AppBarLayoutBehavior()
    private var onStateChangedListener: (Boolean) -> Unit = {}
    var enableExpandAndCollapseByDraggingToolbar: Boolean
        get() = behavior.canDrag
        set(value) {
            behavior.canDrag = value
        }

    var enableExpandAndCollapseByDraggingContent: Boolean
        get() = behavior.acceptsNestedScroll
        set(value) {
            behavior.acceptsNestedScroll = value
        }

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    init {
        addOnOffsetChangedListener(
                AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
                    isAppBarExpanded = verticalOffset == 0
                    onStateChangedListener(isAppBarExpanded)
                })
    }

    override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
        super.setLayoutParams(params)
        (params as CoordinatorLayout.LayoutParams).behavior = behavior
    }

    fun toggleExpandedState() {
        setExpanded(!isAppBarExpanded, true)
    }

    fun setOnExpandAndCollapseListener(onStateChangedListener: (Boolean) -> Unit) {
        this.onStateChangedListener = onStateChangedListener
    }

    private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
        var canDrag = true
        var acceptsNestedScroll = true

        init {
            setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
                override fun canDrag(appBarLayout: AppBarLayout) = canDrag
            })
        }

        override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View,
                                         target: View, nestedScrollAxes: Int, type: Int) = acceptsNestedScroll
    }
}

用法:除了在布局XML文件中使用它之外,您还可以使用以下方法禁用/启用它的扩展:

appBarLayout.enableExpandAndCollapseByDraggingToolbar = true/false

appBarLayout.enableExpandAndCollapseByDraggingContent = true/false