【问题标题】:Topmost View swallows touch events because overlappingTopmost View 吞下触摸事件,因为重叠
【发布时间】:2017-12-07 06:36:27
【问题描述】:

这是我的布局结构:

- 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。覆盖 RecyclerViewdispatchTouchEvent 以执行其逻辑并返回 false 以作为它没有调度其触摸事件的行为,因此 rootview 将继续通过其子视图迭代触摸。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    super.dispatchTouchEvent(ev);
    return false;
}

发生了什么:

RecyclerView 仍然有效,但仍会吞噬所有触摸事件(与以前一样)

2。覆盖RecyclerViewonTouchEvent 以执行其逻辑并返回false。 (注意:我知道这似乎不是解决方案,但我尝试过)

@Override
public boolean onTouchEvent(MotionEvent e) {
    super.onTouchEvent(e);
    return false;
}

发生了什么:

结果与 #1 相同

我用同样的想法做了更多的调整,但效果不佳,所以我现在有点无能为力,希望能得到你们的帮助!

【问题讨论】:

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


    【解决方案1】:

    我有两个建议给你:
    第一个更简单,但假设您要单击 Button 并且它可以出现在RecyclerView 上方,如果是这种情况,您可以简单地更改布局的顺序:

    - 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 )。

    【讨论】:

    • 第一个解决方案是个好主意,但不起作用,因为如果我在按钮上滚动,按钮会将触摸事件吞噬为“选定”,因此 RecyclerView 不会滚动。
    • 第二种解决方案很有趣,可能会奏效,但我的布局比示例中的要复杂得多,所以我宁愿只在万不得已的情况下尝试它
    【解决方案2】:

    我不确定这是多么高效/有效/好的解决方案,但这就是我想到的:如果您在根视图中侦听触摸事件(在您的情况下为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;
        }
    }
    

    这似乎应该可行。

    【讨论】:

    • 谢谢,刚刚尝试了您的解决方案,但似乎无法正常工作。 RecyclerView 仍然只捕获 RecyclerView 触摸事件,不幸的是,其他视图似乎也不受任何触摸的影响。我也不太了解您的目的解决方案 - 此代码将触摸事件应用于其所有兄弟姐妹,但没有 RecyclerView?如果是这样,(即使我们看到它不是发生的行为)它不是必需的行为。我希望 RecyclerView 能够获得触摸事件,而且如果根视图的兄弟姐妹碰到我的触摸的同一区域(通过)
    • 顺便说一句,我必须说,在我将您的代码应用到我的根视图RelativeLayout 之后,RecyclerView 仍然捕获触摸事件,这很奇怪,我希望它在那之后不会捕获任何东西拦截:O
    • onInterceptTouchEvent() 内,您可以看到ViewGroup 发送给它的孩子的所有触摸。在您的情况下会发生什么,ViewGroup 将触摸分派给处理它的RecyclerView,因此没有其他视图有机会响应触摸。我的建议是,不管RecyclerView 处理触摸,您仍然手动将该事件分派给孩子,除了RecyclerView 本身,因为它已经消耗了它,你这样做 想要分派相同的事件两次。
    • Ehm,但是如果根视图截获触摸并手动将其分派给除 RecyclerView 之外的子级,为什么它会得到触摸事件呢?我的意思是,根视图首先获得触摸,拦截它并仅将其分派给其他孩子,这对我来说 RecyclerView 不会获得触摸是有意义的?
    • 不拦截触摸事件。它只是调用super 并返回super 完成的布尔值。所以它只是调解/监听所有触摸并且不拦截它们。
    【解决方案3】:

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

    @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 的子项中的点击:有效。

    更新:

    为什么 FrameLayout 需要高程?

    android:elevation 提供了一种定位任何布局的 z 轴的方法。至于为什么有必要这样做,回答我必须深入研究并用冗长的解释让你厌烦(上面已经写了很多东西)就足以说明MotionEvents 涓涓细流 视图遵循一些基本规则:从根到其子代,首先获取事件的子代(检查事件是否属于它)涓滴xy 轴 中的位置,然后在 z 轴 中的顺序(顶部首先获取事件)。


    因此,在您的情况下,当检测到 MotionEvent 时,会将其传递给具有 3 个子项的 RelativeLayout(RootView),Button 将因位置而被过滤。现在您有 2 个 Views 一个 FrameLayoutRecyclerViewRelativeLayout 的最底部子节点位于 z 轴 的顶部,这意味着 RecyclerView 事件将首先分派给它(这自然会消耗事件)。但是,如果出现未消耗事件的情况(您的第一次尝试),则该事件将传递给下一个子 View,直到没有更多子 Views 离开此时父 (@987654339 @) 将假定事件属于它自己并调用它自己的onInterceptTouchEvent()。在此过程中,如果 View 消耗了该事件,则所有后续调用都将直接传递给它(您只能使两者之一成为 FrameLayoutRecyclerView 工作) .

    上述所有解释都意味着规则手册 只有一个View会使用event

    我做了什么

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

    在所有的解释之后

    注释//这部分可以根据需要更改。

    一个更好且可能没有错误的方法是

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

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

    【讨论】:

    • 我将能够在 1 天内测试您的解决方案。同时,我想问一下为什么FrameLayout需要提升?而如果RecyclerView的dispatchTouchEvent调用了根视图的disptachTouchEvent,那岂不是无限递归了?因为它会再次调用RecyclerView的dispatchTouchEvent?
    • @idish 你似乎有点困惑。我不是在调用根视图的调度方法,而是从 FrameLayout 调用 RecyclerView 的 调度方法。至于你的其他问题,我会更新答案。
    • 我明白了,现在这更有意义了,至于高程部分,为什么我们不能只覆盖 RecyclerView 中的 dispatchTouchEvent 方法,并且在它的实现中做和你一样但是 dispatchTouchEvent 到取而代之的是FrameLayout,所以整个海拔部分都幸免于难?此外,按钮不是 FrameLayout 的子项,而是如上所述的根视图。我的下一个问题是:为什么我不能覆盖根视图的 dispatchTouchEvent 并将 dispatchTouchEvent 调用给它的所有子视图,这样如果motionEvent 匹配命中矩形,每个子视图都会吞下它?
    • @idish 你当然可以对 RecyclerView 做同样的事情。我这样做是因为 FrameLayout 跨越了父级的宽度和高度(MotionEvent 可能会或可能不会在跨越布局边界时丢失,但我自己从未检查过它)。第二个查询您可能还没有完成,但我认为默认情况下提升 Lollipop 后答案将是一个 Button。
    • @idish 至于你的第三个问题,我想不出任何理由,除了它可能会占用系统(取决于根在 rect 的孩子数量,似乎不太可能很好),你为什么不尝试一下,让我知道它是否/如何有效。
    猜你喜欢
    • 1970-01-01
    • 2017-03-27
    • 1970-01-01
    • 2019-07-25
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    相关资源
    最近更新 更多