Topmost View因重叠而吞下触摸事件

时间:2017-07-03 23:20:12

标签: java android android-layout android-view android-custom-view

所以这是我的布局结构:

- RelativeLayout (full screen)
  - FrameLayout (full screen)
  - Button (in the middle of the screen)
  - RecyclerView (align parent bottom but with very big padding top to 
    capture the scrolling also in the top of the screen, so it's basically acting like a full screen `RecyclerView`)

是的,这些观点相互重叠。

由于RecyclerView是最顶层的视图,它会捕获屏幕上的所有触摸事件,并吞下它们,防止任何触摸“通过它”到它下面的底层视图。 (注意:根据基础观点,我不是指RecyclerView个孩子,而是其他rootview's个孩子)

我已经阅读了大量关于传播触摸事件和防止吞咽触摸事件等的stackoverflow帖子,即使看起来非常简单的任务,我也无法达到以下效果:

我希望我的RecyclerView捕获触摸事件,因此它会滚动或者其他任何内容。但我希望rootView认为RecyclerView没有捕获事件,并继续将其传递给其他子项(rootview' 儿童)。

这是我试图做的事情:

1 即可。覆盖dispatchTouchEvent的{​​{1}}来执行其逻辑并返回false,因为它没有调度其触摸事件,因此rootview将继续迭代以通过其子视图进行触摸。

RecyclerView

发生了什么:

@Override public boolean dispatchTouchEvent(MotionEvent ev) { super.dispatchTouchEvent(ev); return false; } 仍在运作,但仍会吞下所有触摸事件(与以前一样)

2 即可。覆盖RecyclerView的{​​{1}}以执行其逻辑并返回false。 (注意:我知道它似乎不是解决方案,但我试过了)

onTouchEvent

发生了什么:

与#1相同的结果

我已经用同样的想法做了一些调整,但是它们没有那么好用,所以我现在有点无能为力,并希望得到你们的帮助!

3 个答案:

答案 0 :(得分:0)

我不确定这是多么高效/有效/好的解决方案,但这就是我想到的:如果你在根视图中监听触摸事件怎么办?(在你的情况下为RelativeLayout) ,并将触摸事件发送给该布局的所有子项,但RecyclerView

除外
public class MyRelativeLayout extends RelativeLayout {
    ...

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final boolean intercept = super.onInterceptTouchEvent(ev);

        // getChildCount() - 1, because `RecyclerView` is the last child
        for (int i = 0, size = getChildCount() - 1; i < size; i++) {
            View v = getChildAt(i);
            v.dispatchTouchEvent(MotionEvent.obtain(ev));
        }

        return intercept;
    }
}

这似乎应该有用。

答案 1 :(得分:0)

我有两个建议:
第一个更简单,但假设您要点击Button,它可以显示在 RecyclerView上方,如果是这种情况,您只需更改布局&#39 ;顺序:

- RelativeLayout (full screen)
  - FrameLayout (full screen)
  - RecyclerView
  - Button (in the middle of the screen)

如果你需要更通用的东西,可以这样做:

recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int [] pos = new int[2];
                recyclerView.getLocationOnScreen(pos);
                Rect rect = new Rect();
                button.getHitRect(rect);
                if (rect.contains((int) event.getX() + pos[0], (int) event.getY() + pos[1])) {
                    button.onTouchEvent(event);
                }
                return false;
            }
        });

您需要单独处理所有视图,这不是很好,但它在我的测试中有效。

注意:如果您已将addOnItemTouchListener添加到RecyclerView,则需要将所述功能添加到onInterceptTouchEvent(或两个地方,取决于您的商品和RecyclerView)。

答案 2 :(得分:0)

我不太了解您的问题,但如果目标是不允许RecyclerView使用任何和所有触摸事件,那么只需为android:elevation="1dp"设置FrameLayoutOverride dispatchTouchEvent()一样:

@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
    Logger.log("CustomFrameLayout : dispatchTouchEvent");
    boolean result = super.dispatchTouchEvent(event);

    ((View) getParent()).findViewById(R.id.recyclerView).dispatchTouchEvent(event);

    return result;                 // This part can be changed according to requirement.
}

这可能会或可能不会产生一些不良的副作用,因为我只是自己提出它,但是我已经检查过它没有。

我测试过的案例:

  1. 在RecyclerView中滚动:工作。
  2. 在RecyclerView项目中点击:工作。
  3. 在FrameLayout的儿童中点击:工作。
  4. 更新:

      

    为什么FrameLayout需要提升?

    android:elevation提供了任何布局的z轴位置的方法。对于为什么有必要这样做,回答我必须深入研究并给你一个冗长的解释(已经写了很多东西)就足够了假设MotionEvent按照一些基本规则逐步减少视图:从root到它的子节点,孩子首先获得事件(检查事件是否属于它)逐渐减少它们在 xy轴中的位置的基础,然后是它们在 z轴中的顺序(前一个首先获得事件)。

    因此,在您检测到MotionEvent时,会将其传递给RelativeLayout(RootView),其中有3个子项,Button将因位置而被过滤。现在您有2 ViewsFrameLayoutRecyclerViewRelativeLayout最底层的孩子位于 z轴之上,这意味着RecyclerView事件将首先发送给它(这将自然消耗事件)。但是,如果出现未使用事件(第一次尝试)的情况,则该事件将被传递给下一个孩子View,直到不再有子View为止,此时parent(RelativeLayout)将假定该事件属于自身并调用其自己的onInterceptTouchEvent()。在此过程中,如果View消耗了该事件,则所有后续调用都将直接传递给它(原因是您只能将其中一个调用FrameLayoutRecyclerView 工作)。

      

    上述所有解释均指规则手册 ,只有一个View会使用event

    我做了什么

    我只是提升 FrameLayout所以它将成为事件的官方接收者。覆盖dispatchTouchEvent()并将相同的事件发送到其邻近的RecyclerView,认为该事件已通过常规模式发送给它。

    现在解释完毕后

      

    评论//这部分可以根据要求进行更改。

    更好,可能没有错误的方法

    boolean result2 = ((View) getParent()).findViewById(R.id.recyclerView).dispatchTouchEvent(event);
    
    return result || result2;
    

    因此,如果任何Views消费了该事件,则 root 认为它已被消费。