问题 防止RecyclerView消耗触摸事件


问题

我试图模拟Google Play应用程序的确切行为。

目前,当您滚动浏览类似“热门游戏”的类别并触摸列表时,该单元格以及 RecyclerView 处理触摸事件,你可以告诉他们正在处理它,因为当你按住手指时出现波纹以及滚动停止。或者,如果您在列表仍在移动时单击一个单元格,则会立即打开该内容。

默认行为是滚动在您移动时将手指放下时立即停止,然后消耗该触摸事件,这意味着细胞永远不会被告知该触摸。当您的应用程序主要内容在a中时,这非常令人恼火 RecyclerView 如果用户连续滚动然后单击一个单元格,则需要他们单击两次,因为通过停止滚动消耗了第一次单击,即使列表几乎没有移动。

我到目前为止所做的一切

起初我以为里面肯定会有某种旗帜 RecyclerView 告诉它不要消耗触摸事件。我查看了Android源代码并通过Android文档路由无济于事。

然后我试着捕捉触摸事件 onInterceptTouchEvent 并手动告诉我的观点,它被触动;

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    View myCell = recyclerView.findChildViewUnder(event.getX(), event.getY());
    myCell.dispatchTouchEvent(event);
    return super.onInterceptTouchEvent(event);
}

这里, dispatchTouchEvent 似乎没有做任何事情,即使我得到的视图有我在适配器中设置的相应标签。

我试过用我的编码机枪在各个角度拍摄这个问题;和谁玩 requestDisallowInterceptTouchEvent,手动呼叫 onTouch 并且压倒各种各样的听众 RecyclerView 超越。

似乎没有什么工作。我敢肯定必须有一个优雅的解决方案,我已经在精神错乱的道路上掠过一个重要的细节?特别是因为谷歌自己的应用程序以这种方式运行。

请帮忙! :) 谢谢。

解决方案

感谢Jagoan Neon已找到解决方案。我最终将他的答案包含在一个习惯中 RecyclerView 为了方便使用。

public class MultiClickRecyclerView extends RecyclerView {
   public MultiClickRecyclerView(Context context) {
        super(context);
   }

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

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

   @Override
   public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN && this.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            this.stopScroll();
        }
        return super.onInterceptTouchEvent(event);
   }
}

2416
2018-03-29 16:33


起源

你知道为什么手动停止滚动会将touch事件发送到recyclerView里面的itemView吗? - litaoshen


答案:


首先,您应该定义一个OnItemTouchListener,可能是这样的全局变量:

RecyclerView.OnItemTouchListener mOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            Log.d(TAG, "onInterceptTouchEvent: click performed");
            rv.findChildViewUnder(e.getX(), e.getY()).performClick();
            return true;
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
};

为什么选择ACTION_DOWN和SCROLL_STATE_SETTLING?

当您在RecyclerView上执行触摸时,将触发ACTION_DOWN运动事件,并且在确切的时间点,RecyclerView在尝试停止滚动时将其滚动状态更改为SCROLL_STATE_SETTLING。而这正是您想要执行点击的时间。

并记住返回true,以便告诉您的RecyclerView您已经使用了MotionEvent。否则,您的performClick()可能会被多次调用。

将它添加到onResume的RecyclerView并在onPause中将其删除,然后你就完成了设置;)

UPDATE

当您执行findChildViewUnder时添加一个null检查器 - 当您没有触及特定单元格时它返回null。

View childView = rv.findChildViewUnder(e.getX(), e.getY());
if (childView != null) childView.performClick();

更新2

事实证明,停止RecyclerView滚动就足够了。改为:

@Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING)
            rv.stopScroll();
        return false;
    }

10
2018-03-30 04:02



我今天早上实现了你的答案,我们快到了!真奇怪;这个解决方案似乎可以在大约30%的时间内工作,这取决于我事先拖动的速度有多慢。我一直在注销运动事件和滚动状态,似乎没有什么区别,它什么时候有效,什么时候没有。如果我快速滚动列表然后点击并按住我的手指,它几乎从不注册触摸并显示波纹(就像Google Play应用程序那样)。它是否一直为你工作?我正在使用2跨度计数网格布局,虽然我不认为这会有所作为。 - vguzzi
你看过我的第二次编辑吗? - Jagoan Neon
是的,我目前正在停止滚动视图实现。我也尝试过以前的建议。 - vguzzi
它对我来说很好 - 它也显示了涟漪和东西,就像Play应用程序一样。你使用哪个支持lib版本? - Jagoan Neon
我认为这种行为应该是RecyclerView的默认行为 - Revaz Shalikashvili