防止RecyclerView消耗触摸事件

23

问题

我试图模拟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和更多的监听器。

似乎什么都不起作用。我确信必须有一种优雅的解决方案,而我在疯狂寻找答案的过程中可能忽略了重要细节?特别是因为谷歌自己的应用程序也会表现出这种方式。

请帮忙!:) 谢谢。

解决方案

感谢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的触摸事件吗? - litaoshen
1个回答

30

首先,你应该定义一个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。

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

是的,我目前正在停止滚动视图的实现上。我也尝试了之前的建议。 - vguzzi
它对我来说运行得很好 - 它也显示了涟漪和其他东西,就像Play应用程序一样。您使用的是哪个支持库版本? - Jagoan Neon
23.2.1,现在正在升级到24 alpha版本,看看是否有任何变化。 - vguzzi
那个和我用的一样。只有当我使用stopScroll技术时才会显示涟漪效果。 - Jagoan Neon
3
我认为这种行为应该成为RecyclerView的默认设置。 - Revaz Shalikashvili
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接