防止 RecyclerView 消费触摸事件

Posted

技术标签:

【中文标题】防止 RecyclerView 消费触摸事件【英文标题】:Preventing RecyclerView from consuming touch events 【发布时间】:2016-07-17 08:21:28 【问题描述】:

问题

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

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

默认行为是,如果您在移动时将手指放在下方,则滚动会立即停止,然后它会消耗该触摸事件,这意味着单元格永远不会被告知该触摸。当您的应用程序主要内容位于 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 及以后的所有听众。

似乎没有任何效果。我确信必须有一个优雅的解决方案来解决这个问题,而且我在通往精神错乱的道路上某个地方浏览了一个重要的细节?尤其是因为 Google 自己的应用程序就是这样运行的。

请帮忙! :) 谢谢。

解决方案

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

【问题讨论】:

你知道为什么手动停止滚动会得到recyclerView里面的itemView的touch事件吗? 【参考方案1】:

首先你应该定义一个 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 将其删除,一切就绪 ;)

更新

在您执行 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;
    

【讨论】:

我今天早上实施了您的回答,我们快到了!真奇怪;这个解决方案似乎在 30% 的时间里都能奏效,具体取决于我事先拖得有多慢。我一直在注销运动事件和滚动状态,它的工作时间和不工作时间似乎没有区别。如果我快速滚动列表,然后点击并按住我的手指,它几乎不会记录触摸并显示波纹(就像 Google Play 应用程序一样)。它是否经常为您工作?我使用的是 2 跨度计数网格布局,虽然我不认为这会有所作为。 是的,我目前正在执行停止滚动视图。我也尝试了之前的建议。 它对我来说工作正常 - 它还显示波纹和东西,就像 Play 应用程序一样。您使用的是哪个支持库版本? 23.2.1,现在升级到 24 alpha 看看是否有任何变化。 我认为这种行为应该是 RecyclerView 的默认行为

以上是关于防止 RecyclerView 消费触摸事件的主要内容,如果未能解决你的问题,请参考以下文章

Recyclerview在Android中notifyItemChanged后防止触摸事件

eowise 无法捕获 Sticky Header (RecyclerView) 的子视图的触摸事件

防止多点触控的 UIButton 触摸事件

jquery防止触摸悬停功能

Android RecyclerView点击事件处理

防止父视图在子视图作用于它后接收触摸事件