当前位置: 首页 > 知识库问答 >
问题:

如何在使用setNestedScrollingEnabled(false)时避免滚动本身受阻?

郎喜
2023-03-14

我们有一个相当复杂的布局,其中有CollapsingToolbarLayout,底部还有一个RecycerView。

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

这通常很好用。

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

}

起初我认为这是因为其他原因(我认为这是与Drawer布局的奇怪组合),但后来我找到了一个最小的示例来展示它,这和我想的一样:这都是因为SetNestedScrollingEnabled。

我试图在谷歌的网站(这里)上报道这个问题,希望如果它是一个真正的错误,它会得到修复。如果你想尝试一下,或者看这个问题的视频,去那里,因为我不能在这里上传他们所有(太大和太多的文件)。

有办法克服这一点吗?

是否有其他方法来调用setNestedScrollingEnabled的这个函数?一个没有任何滚动或锁定CollapsingToolbarLayout状态的问题?

共有1个答案

梁宏才
2023-03-14

这是实现与此答案相同目标的另一种方法。虽然那个答案使用了反射,但这个答案没有,但推理仍然是一样的。

为什么会这样?

问题是RecycerView有时会对成员变量MSCrolloffSet使用陈旧值。MScrolloffset仅设置在RecycerView中的两个位置:DispatchnestedPrescrollDispatchnestedScroll。我们只关心dispatchnestedprescroll。此方法在处理MotionEvent.action_move事件时由RecycerView#OnTouchEvent调用。

在此视图消耗嵌套滚动的任何部分之前,分派正在进行的嵌套滚动的一个步骤。

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

...

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

RecycerView#DispatchNestedPrescroll解析为NestedScrollingChildHelper中同名的方法。

RecycerView调用DispatchnestedPrescroll时,MScrolloffset用作offsetinwindow参数。因此,对offsetinwindow所做的任何更改都直接更新mscrolloffsetdispatchnestedprescroll更新MScrolloffset只要嵌套滚动有效。如果嵌套滚动无效,则MScrolloffset不会更新,并且继续使用DispatchNestedPrescroll上次设置的值。因此,当关闭嵌套滚动时,MScrolloffset的值立即变得陈旧,但RecycerView继续使用它。

DispatchnestedPrescroll返回时,MScrolloffset[1]的正确值是为输入坐标跟踪而调整的量(见上文)。在RecycerView中,以下行调整y触摸坐标:

mLastTouchY = y - mScrollOffset[1];

假设MScrolloffset[1]为-30(因为它已经过时,应该为零),那么MlastTouchy将关闭+30像素(--30=+30)。这种误判的效果是,它将看起来触摸发生在屏幕下更远的地方,而不是真正的触摸。因此,缓慢的向下滚动实际上会向上滚动,向上滚动会更快。(如果向下滚动的速度足以克服这个30px障碍,那么向下滚动将会发生,但速度会比应该发生的慢。)向上滚动会太快,因为应用程序认为已经覆盖了更多的空间。

MScrolloffset将继续作为陈旧变量,直到打开嵌套滚动,并且DispatchnestedPrescroll再次报告MScrolloffset中的正确值。

方法

由于MScrolloffset[1]在某些情况下有一个陈旧的值,因此目标是在这些情况下将其设置为正确的值。当没有发生嵌套滚动时,即当AppBar展开或折叠时,此值应为零。不幸的是,MScrolloffsetRecycerView本地的,没有设置器。为了在不诉诸反射的情况下获得对MScrolloffset的访问权限,将创建一个自定义的RecycerView,该自定义的DispatchnestedPrescroll将重写。第四个变量是offsetinwindow,这是我们需要更改的变量。

每当为RecycerView禁用嵌套滚动时,就会出现陈旧的MScrolloffset。我们将附加的一个条件是AppBar必须是空闲的,因此我们可以放心地说MScrolloffset[1]应该为零。这不是问题,因为CollapsingToolbarLayout在滚动标志中指定Snap

下面给出了ScrollingActivity的更新源文件,以及自定义的RecycerView-MyRecycerView。必须更改XML布局文件以反映自定义MyRecycerView

滚动活动

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

MyRecycerView

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";
}
 类似资料:
  • 问题内容: 如何保护我们的Web应用程序免受XSS攻击?如果一个应用程序不进行任何特殊字符转换,就很容易受到攻击。 问题答案: 您应该先将所有输入转回HTML,然后再将其输出回给用户。一些参考: OWASP XSS(跨站点脚本)预防备忘单 考虑从Apache Commons Lang使用 或从Spring使用 JSP / Servlet Web应用程序中的XSS预防

  • 问题内容: 现在,我正在尝试对汇编程序进行编码,但我不断收到此错误: 我目前有以下代码: 感谢帮助。 问题答案: 您通常使用默认值 如果key在字典中,则返回key的值,否则返回默认值。如果未提供default,则默认为None,因此此方法永远不会引发KeyError。 因此,当您使用循环时,将如下所示: 打印: 如果要显式检查键是否在字典中,则必须检查键是否在字典中(无索引!)。 例如: 但是该

  • 我使用的是一个包含DATETIME列的SQLite数据库。jOOQ default将日期时间列绑定到java。sql。时间戳。查询包含DATETIME列的表会导致每列出现NumberFormatException(已处理)。 我使用jOOQ 3.11.9。 异常在parse方法,当它第一次尝试将时间戳字符串值转换为数字时。 查看和DefaultTimestampBinding方法时,时间戳总是ge

  • 问题内容: 在我的一个Selenium测试案例中,我有一个问题,我不想拥有MouseOver效果。这是我的工作: 点击“登录”按钮(页面右上方) 等待页面加载 单击搜索结果中的“购买”按钮(页面右中角)。 问题是,在“登录”和“购买”之间的中间存在一个带有MouseOver效果的“购物篮”链接。因此,当我在“登录”按钮上调用Click(),然后在“购买”按钮上调用时,我会触发MouseOver,这

  • 我听说过很多关于在Excel VBA中使用的憎恶是可以理解的,但我不确定如何避免使用它。我发现,如果我能够使用变量而不是函数,那么我的代码将更加可重用。但是,如果不使用,我不确定如何引用东西(如等)。 我已经找到了这篇关于范围的文章和这个关于不使用select的好处的例子,但是我找不到任何关于如何使用的东西。

  • 问题内容: 这看起来很丑。可选选项的优点在这里完全消失了。我读到应该使用或代替。但是,如果我更换每个吸气剂,真的有好处吗? 与 您知道这里的一些常见或最佳做法吗? 问题答案: 您可以使用 关键是仅在可选函数不为空时才评估映射函数,否则结果将保留为空。如果可选为空,将返回。