例子
开发中会有一些需求,要让子控件超出父布局,例如:
这时有一种简单的方式实现,就是使用 clipChildren=false
。之前一直知道这个属性,实际开发中却很少用到,直到今天用到了,并且要进行点击事件处理时,遇到了问题。
布局如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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 | center_icon.getRootView().setOnTouchListener(new View.OnTouchListener() { |
这里我们用到了一个重要方法 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.getRawX
、event.getRawY
一定要用 raw 坐标,获取到的全局坐标系才能跟view.getGlobalVisibleRect(Rect r)
获取到的全局坐标范围 rect 相匹配。
补充
为了更方便的处理此类问题,封装了一个全局触摸事件基类 : Android View 全局触摸事件监听基类