Android触摸事件的分发机制是学习自定义控件的过程中必须理清的知识点。今天我们就讲一下Android的触摸事件分发的全过程。
首先引进一个列子
<?xml version="1.0" encoding="utf-8"?>
<test.weizhong.com.customviewtest.ViewGroupBottom xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#256845"
tools:context="test.weizhong.com.customviewtest.Main2Activity">
<test.weizhong.com.customviewtest.ViewGroupMiddle
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="46dp"
android:background="#668416">
<test.weizhong.com.customviewtest.ViewGroupTop
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="46dp"
android:background="#B48521"/>
</test.weizhong.com.customviewtest.ViewGroupMiddle>
</test.weizhong.com.customviewtest.ViewGroupBottom>
我们先写一个三层嵌套的布局,并且为每一层设置不同颜色,以便于点击的时候能区分自己点击了哪一层。
然后我们在每一层的布局里面为dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()打一个log,
来观察方法的调用顺序。
public class ViewGroupBottom extends LinearLayout{
public ViewGroupBottom(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean t = super.dispatchTouchEvent(ev);
Log.e("TAG","ViewGroupBottom : dispatchTouchEvent() == " + t);
return t;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean t = super.onInterceptTouchEvent(ev);
Log.e("TAG","ViewGroupBottom : onInterceptTouchEvent() == " + t);
return t;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean t = super.onTouchEvent(event);
Log.e("TAG","ViewGroupBottom : onTouchEvent() == " + t);
return t;
}
}
public class ViewGroupMiddle extends LinearLayout{
public ViewGroupMiddle(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean t = super.dispatchTouchEvent(ev);
Log.e("TAG","ViewGroupMiddle : dispatchTouchEvent() == " + t);
return t;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean t = super.onInterceptTouchEvent(ev);
Log.e("TAG","ViewGroupMiddle : onInterceptTouchEvent() == " + t);
return t;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean t = super.onTouchEvent(event);
Log.e("TAG","ViewGroupMiddle : onTouchEvent() == " + t);
return t;
}
}
public class ViewGroupTop extends LinearLayout{
public ViewGroupTop(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean t = super.dispatchTouchEvent(ev);
Log.e("TAG","ViewGroupTop : dispatchTouchEvent() == " + t);
return t;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean t = super.onInterceptTouchEvent(ev);
Log.e("TAG","ViewGroupTop : onInterceptTouchEvent() == " + t);
return t;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean t = super.onTouchEvent(event);
Log.e("TAG","ViewGroupTop : onTouchEvent() == " + t);
return t;
}
}
首先点击ViewGroupTop
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onInterceptTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onInterceptTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupTop : onInterceptTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupTop : onTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupTop : dispatchTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : dispatchTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onTouchEvent() == false
12-14 13:58:34.943 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : dispatchTouchEvent() == false
我们发现在默认情况下先调用了ViewGroupBottom 的onInterceptTouchEvent(),然后是ViewGroupMiddle的onInterceptTouchEvent()
最后是ViewGroupTop 的 onInterceptTouchEvent(),onTouchEvent()。
我们再点击ViewGroupMiddle
12-14 14:03:09.813 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onInterceptTouchEvent() == false
12-14 14:03:09.813 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onInterceptTouchEvent() == false
12-14 14:03:09.813 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onTouchEvent() == false
12-14 14:03:09.813 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : dispatchTouchEvent() == false
12-14 14:03:09.813 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onTouchEvent() == false
12-14 14:03:09.813 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : dispatchTouchEvent() == false
发现触摸事件只传递到ViewGroupMiddle,就调用了ViewGroupMiddle的onTouchEvent()了。
所以我们得出了触摸事件的传递方向 1、触摸事件是由布局的根部往子控件传递的,而且只传递到你点击的那一层子布局。
接下来我们修改ViewGroupMiddle的onInterceptTouchEvent的返回值为true,再点击ViewGroupTop。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean t = super.onInterceptTouchEvent(ev);
Log.e("TAG","ViewGroupMiddle : onInterceptTouchEvent() == " + t);
return true;
}
12-14 14:09:49.633 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onInterceptTouchEvent() == false
12-14 14:09:49.633 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onInterceptTouchEvent() == false
12-14 14:09:49.633 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onTouchEvent() == false
12-14 14:09:49.633 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : dispatchTouchEvent() == false
12-14 14:09:49.633 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onTouchEvent() == false
12-14 14:09:49.633 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : dispatchTouchEvent() == false
我们发现触摸事件从ViewGroupBottom传递到了ViewGroupMiddle,但是并没有传递到ViewGroupTop。而是又调用了ViewGroupMiddle的
onTouchEvent(),和dispatchTouchEvent()。所以onInterceptTouchEvent()设置为true是阻断触摸事件往子控件传递的。阻断了之后就
调用自己的onTouchEvent()来处理事件,并且在处理完事件之后调用dispatchTouchEvent()来上报父控件我是否处理了该事件。那么
我们要上报给父控件什么值,又会产生什么效果呢?
我们刚才的ViewGroupMiddle里面dispatchTouchEvent()返回的是默认值,也就是false,父控件则调用了onTouchEvent() 和
dispatchTouchEvent()来回应。如果我们把ViewGroupMiddle里面dispatchTouchEvent()的返回值改为true呢?又有什么效果?
我们来试一下,把ViewGroupMiddle里面dispatchTouchEvent()的返回值改为true,并且点击ViewGroupTop
12-14 14:22:33.363 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : onInterceptTouchEvent() == false
12-14 14:22:33.363 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onInterceptTouchEvent() == false
12-14 14:22:33.363 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : onTouchEvent() == false
12-14 14:22:33.363 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupMiddle : dispatchTouchEvent() == false
12-14 14:22:33.363 30747-30747/test.weizhong.com.customviewtest E/TAG: ViewGroupBottom : dispatchTouchEvent() == true
我们发现把ViewGroupMiddle里面dispatchTouchEvent()的返回值改为true之后父控件ViewGroupBottom的onTouchEvent()方法不再调用了。
这是为什么呢?原来子控件返回的dispatchTouchEvent()值对父控件产生了影响。
由此我们可以假设触摸事件的传递过程是,父控件传递触摸事件过来之后,我们可以通过onInterceptTouchEvent()的返回值来告诉
子控件我要不要处理这个事件,如果我要处理这个事件则调用onTouchEvent(),在onTouchEvent()里面处理,子控件就不必接收该
事件了。处理完了事件之后又通过dispatchTouchEvent()的返回值告诉父控件我是否处理好了,如果处理好了就返回true,让父控件
再向上告知我已经处理完了就可以了,父控件就不必要处理了。如果我没处理好则返回false,告知父控件并让父控件去处理。总之
每一级的控件重复这样的逻辑。
所以,其实触摸事件的传递就像是一个公司的任务分配流程一样。比如老板有一个需求,他告知项目经理,项目经理如果觉得这个
事情只能由他来做,那么他自己就做了,不再通知下属。当他做完了就直接反馈给老板说我做完了。如果他最终没做好,就通知
老板说我没做好,由老板自己再去想办法做。
如果项目经理觉得他自己做不了,但是他的下属程序猿能做,那么他就会把事件传递给程序猿。程序猿再根据自己的情况自己去处理。