作为触屏手机,触摸事件是最基本的事件,没有之一。在实际的触屏事件响应的过程中,public boolean onInterceptTouchEvent(MotionEvent ev)和public boolean onTouchEvent(MotionEvent event)是最重要的两个函数。下面将通过代码来展示一次触摸事件的处理流程,一次完整的触摸包括DOWN,MOVE,UP。
首先,先说下public boolean onInterceptTouchEvent(MotionEvent ev)这个函数,官方文档就不贴在这了,因为即使是官方文档其说明也是很复杂的,这里先只说总结性的一句话,onInterceptTouchEvent本身不会消耗任何触摸事件,只能影响touch事件的处理顺序,touch事件在被onTouchEvent返回true表示消耗之前会一直传递下去。当viewGroup收到触摸事件时,onInterceptTouchEvent会在onTouchEvent之前被调用,onInterceptTouchEvent的返回值决定了触摸事件是否立即被viewGroup本身的onTouchEvent响应。具体看下面。(相关代码已经上传http://www.oschina.net/code/snippet_272860_17818,有兴趣可以下载自己做实验研究)
首先自定义三个layout,ALayout,BLayout,CLayout均继承自RelativeLayout。,其中在相关函数中打印出日志:
ALayout
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e(
"sssssssssssssssss"
,
"InterceptTouchEvent A "
+ Common.getAction(ev));
return
false
;
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.e(
"sssssssssssssssss"
,
"onTouchEvent A"
+ Common.getAction(event) );
return
false
;
}
|
BLayout
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e(
"sssssssssssssssss"
,
"InterceptTouchEvent B "
+ Common.getAction(ev));
return
false
;
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.e(
"sssssssssssssssss"
,
"onTouchEvent B"
+ Common.getAction(event) );
return
false
;
}
|
CLayout
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e(
"sssssssssssssssss"
,
"InterceptTouchEvent C "
+ Common.getAction(ev));
return
false
;
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.e(
"sssssssssssssssss"
,
"onTouchEvent C"
+ Common.getAction(event) );
return
false
;
}
|
下面的是布局文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:tools
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
tools:context
=
".MainActivity"
>
<
com.example.testaaaa.ALayout
android:id
=
"@+id/aa"
android:layout_width
=
"200dip"
android:layout_height
=
"200dip"
android:layout_alignParentLeft
=
"true"
android:layout_alignParentTop
=
"true"
android:layout_marginLeft
=
"61dp"
android:layout_marginTop
=
"68dp"
android:background
=
"#ff0000ff"
>
<
com.example.testaaaa.BLayout
android:id
=
"@+id/bb"
android:layout_width
=
"150dip"
android:layout_height
=
"150dip"
android:layout_centerHorizontal
=
"true"
android:layout_centerVertical
=
"true"
android:background
=
"#ffff0000"
>
</
com.example.testaaaa.BLayout
>
</
com.example.testaaaa.ALayout
>
<
com.example.testaaaa.CLayout
android:id
=
"@+id/cc"
android:layout_width
=
"100dip"
android:layout_height
=
"100dip"
android:layout_centerHorizontal
=
"true"
android:layout_centerVertical
=
"true"
android:background
=
"#ff00ff00"
>
</
com.example.testaaaa.CLayout
>
</
RelativeLayout
>
|
结合xml中的布局可知,AB是层级或者父子关系,AC是平级或者兄弟关系。下面先点击“1”区域,打印出的日志如下:
整个流程是这样的:因为AB是层级关系,事件优先被下发到A的onInterceptTouchEvent中,因为其返回值为false,因此A的onTouchEvent不会被立即调用,然后触摸事件继续下发到B中,B的onInterceptTouchEvent被调用,因为其也返回false,触摸事件应该继续下发给B的子视图,但是B没有childView,于是B的onTouchEvent被调用,又因其返回false表明事件没有被消耗,于是A的onTouchEvent被调用。(PS:下面的所有的实验都是在ABC相关函数都是返回false的基础上修改,每次都并且只修改一个值)
实验1:同样点击“1”区域,A的onInterceptTouchEvent中返回true,此时B的onInterceptTouchEvent和onTouchEvent都不会被调用
实验2:同样点击“1”区域,A的onTouchEvent返回true
实验3:同样点击“1”区域,B的onTouchEvent返回true,此时A的onTouchEvent不会被调用。
下面将ABC中onInterceptTouchEvent和onTouchEvent都返回false。这次点击“3”区域,日志如下:
这次的流程是这样的:AC为平级关系,因为C是盖在A上的,因此触摸事件先被下发到C中,C的onInterceptTouchEvent和onTouchEvent被调用,因为C中onTouchEvent返回false,因此触摸事件没有被消耗并继续被下发到A
实验4:同样点击“3”区域,但是C中onInterceptTouchEvent返回true,因为C没有childView因此不会有变化
实验6:同样点击“3”区域,但是C中onTouchEvent返回true,因为事件被C消耗,A中onInterceptTouchEvent和onTouchEvent不会被调用。
实验7:同样点击“3”区域,但是A中onTouchEvent返回true
下面将ABC中onInterceptTouchEvent和onTouchEvent都返回false。这次点击“2”区域,如果已经对touch事件的处理过程有了一定认识后,应该可以直接分析出吧。下图:
这个只是综合区域“1”和“3”的情形。
Touch事件的处理流程:
1.一旦某个ViewGroup获得了ACTION_DOWN的事件,会根据深度优先的算法遍历以该ViewGroup为根节点的view树
2.如果点击的位置在被遍历到的childView区域中,childView是groupview的话其onInterceptTouchEvent将被调用,这个过程会一直进行下去
3.直到某个viewX的onInterceptTouchEvent返回了true,或者一直遍历到某个叶viewX,然后该viewX的onTouchEvent被调用
4.如果viewX的onTouchEvent返回false,则该viewX的父节点viewY的onTouchEvent被调用,这个过程会一直下去
5.如果直到根节点的onTouchEvent都返回false,那么后续的ACTION_MOVE和ACTION_UP将被这棵树里面的所有的view忽略
6.当某个viewX在onTouchEvent中返回true后,ACTION_MOVE和ACTION_UP会从根节点开始按照深度优先的算法依次调用onInterceptTouchEvent,但是和ACTION_DOWN不同,该过程只持续到viewX的父节点为止,然后viewX的onInterceptTouchEvent不被调用,而直接调用onTouchEvent(查看实验3的日志)
7.如果在ACTION_MOVE或者ACTION_UP在被viewX的onTouchEvent处理之前,某个viewZ的onInterceptTouchEvent返回true,表明viewZ要处理接下来的触摸事件,那么在viewZ的onTouchEvent被调用之前,onInterceptTouchEvent调用还会继续下去,只不过之后的view的onInterceptTouchEvent中的ACTION变成了ACTION_CANCEL,直到viewX中的onTouchEvent处理了这个ACTION_CANCEL后,viewZ的onTouchEvent才被调用,后续事件将只被viewZ的onTouchEvent处理。