作 为触屏手机,触摸事件是最基本的事件,没有之一。在实际的触屏事件响应的过程中,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响应。具体看下面。
首先自定义三个layout,ALayout,BLayout,CLayout均继承自RelativeLayout。,其中在相关函数中打印出日志:
ALayout
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e("sssssssssssssssss", "InterceptTouchEventA " + 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
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e("sssssssssssssssss", "InterceptTouchEventB " + 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
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e("sssssssssssssssss", "InterceptTouchEventC " + 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;
}
下面的是布局文件:
<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处理。