Android超出父布局控件点击事件失效问题处理

例子

开发中会有一些需求,要让子控件超出父布局,例如:

image-20200315143807822

这时有一种简单的方式实现,就是使用 clipChildren=false 。之前一直知道这个属性,实际开发中却很少用到,直到今天用到了,并且要进行点击事件处理时,遇到了问题。

  • 布局如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
    android:layout_gravity="bottom"
    android:background="@drawable/bg1"
    android:layout_width="match_parent"
    android:layout_height="60dp" >
    <ImageView
    android:id="@+id/center_icon"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="-60dp"
    android:src="@drawable/icon1"
    android:layout_width="120dp"
    android:layout_height="120dp" />
    </FrameLayout>
    </FrameLayout>

看起来一切都很好,但是这里会出现一个问题,当给 center_icon 设置点击事件时,会发现超出父布局的部分,不会响应点击事件。

并且,不只是 setOnClickListener 不起用作用, 就连setOnTouchListener 也监听不到任何触摸事件。

究其原因,是因为超出父布局的控件区域,已经不在父布局的触摸范围之内的,当发生触摸时,不会被 center_icon 的父布局响应,自然也不会传递到子控件上了。

解决方案

通过上面分析,我们知道center_icon超出父布局的部分无法响应点击事件的原因,那么也就好解决了。

虽然 center_icon 不在父布局的触摸范围内了,但是它依然在整个当前页面的根View范围内,那么我们对更上层的布局节点进行触摸事件的监听,然后判断点击事件是否落在了 center_icon 所处的坐标范围内,这样就能完成对 center_icon 的点击事件响应了。

处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
center_icon.getRootView().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP){
Rect r = new Rect();
center_icon.getGlobalVisibleRect(r);
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
if(r.contains(rawX,rawY)){
doCenterClick();
return true;
}else {
WalletLogger.v(getLogTag(),"contains");
}
}
return false;
}
});

这里我们用到了一个重要方法 view.getGlobalVisibleRect(Rect r)

  • View.getGlobalVisibleRect(Rect r)

    如果这个视图的某些部分没有被它的任何父视图裁剪,那么在全局(根)坐标中用r返回该区域

总结一下分为三步:

① 获取到 center_icon 的坐标范围区域 rect

② 通过 onTouch 事件获取到点击事件在屏幕中的坐标 rawX, rawY

③ 结合 Rect.contains(x,y) 方法,可以判断出点击事件是否落在了 center_icon 的点击范围内

这样就完成了对center_icon 点击事件的响应。

  • 小注意事项:

    ① 要对 event.getAction() == MotionEvent.ACTION_UP 进行一次判断处理,否则一次触摸会有多次响应 onTouch ,造成重复触发点击事件。

    event.getRawXevent.getRawY 一定要用 raw 坐标,获取到的全局坐标系才能跟 view.getGlobalVisibleRect(Rect r) 获取到的全局坐标范围 rect 相匹配。

补充

为了更方便的处理此类问题,封装了一个全局触摸事件基类 : Android View 全局触摸事件监听基类

文章作者: 普通程序员
文章链接: https://programmerauthor.github.io/2020/03/15/android-clipchildren-click/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 普通程序员