前言:本篇博文主要分析关于onTouchEvent,dispatchTouchEvent和onInterceptTouchEvent这三个方法的作用和它们之间的关系,通过流程图来解释触摸事件(MotionEvent)是怎么在三个方法中传递的;
为了了解三个方法之间的关系,我们通过一个demo来分析它们之间的关系,demo主要内容为在一个activity上设置两个布局控件,一个为LinearLayout,一个是TextView,两个控件都自定义重写了它们的onTouchEvent、dispatchTouchEvent和onInterceptTouchEvent方法,并用logcat打印出各个方法相应的log标记,通过触摸控件后log的打印顺序就可知道他们的调用顺序了,通过调用顺序去分析事件的传递流程,如下图所示为activity的视图
其中红色区域为Textview的视图,底下绿色区域为LinearLayout的视图,最底下蓝色区域为activity的视图;下面贴上代码:
public class GestureDemo extends Activity {
public final static String TAG = "GestureDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"Activity onTouchEvent. action="+getAction(event));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"Activity dispatchTouchEvent. action="+getAction(ev));
return super.dispatchTouchEvent(ev);
}
}
public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context,attrs,defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(GestureDemo.TAG,"MyLinearLayout dispatchTouchEvent. action="+GestureDemo.getAction(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(GestureDemo.TAG,"MyLinearLayout onInterceptTouchEvent. action="+GestureDemo.getAction(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(GestureDemo.TAG,"MyLinearLayout onTouchEvent. action="+GestureDemo.getAction(event));
return super.onTouchEvent(event);
}
}
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs){
super(context,attrs);
}
MyTextView(Context context, AttributeSet attrs, int defStyleAttr){
super(context,attrs,defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(GestureDemo.TAG,"MyTextView dispatchTouchEvent. action="+GestureDemo.getAction(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(GestureDemo.TAG,"MyTextView onTouchEvent. action="+GestureDemo.getAction(event));
return super.onTouchEvent(event);
}
}
注: 每一次屏幕触摸后的手势事件都是连续地由一个down事件、若干move事件和一个up事件组成,每次都必须是down事件先发生,最后一个是up事件;
- 触摸事件:每一个down事件、move事件或up事件都为一个独立的触摸事件;
- 手势事件:由一系列连续的触摸事件组成的一个完整的手势,比如单击手势事件为down-up, 滑动事件为down-move…move-up;
下面通过逐个改变各个方法的返回值,分析它们之间的传递图;
一、 关于onTouchEvent事件
1. 所有方法都返回父类的方法,即MotionEvent没有被子控件消耗掉,最终只有在activity的onTouchEvent消失掉(系统默认传递流程);
2. Activity的onTouchEvent方法返回true,其事件传递流程和上一个相同
3. MyLinearLayout的onTouchEvent返回true
4. MyTextView的onTouchEvent返回true;
总结:以上四张图中,只改动onTouchEvent的返回值,其他方法都调用父类方法,也就是传递流程都为系统默认的流程,没有人为干预,图1即是默认的系统流程,而图2,3,4通过改变onTouchEvent的返回值为true来终止事件的继续传递;从图中可知,当屏幕触摸发生时,down事件每次都是由系统先调用最底层的activity的dispatchTouchEvent事件,然后继续往上传递到最顶层控件,然后由onTouchEvent往下传递,按照默认传递顺序,如果哪个控件返回true,事件就在那个控件停止,即被这个控件消耗掉,如:activity和MyLinearLayout的onTouchEvent都返回true,那么事件只传递到MyLinearLayout的onTouchEvent,因为事件先经过MyLinearLayout的onTouchEvent,先被它截住了;而后续的move、up事件则会根据上个down事件最终在哪个控件被消耗掉,然后按照默认系统流程传递到该控件的dispatchTouchEvent后直接传递给自己的onTouchEvent,不会再往上层控件传递;
二、关于dispatchTouchEvent
1. MyLinearLayout的dispatchTouchEvent返回true
2. MyTextView的dispatchTouchEvent返回true
3. MyLinearLayout的dispatchTouchEvent返回false
4. MyTextView的dispatchTouchEvent返回false
总结:dispatchTouchEvent从方法名大约就可以知道其功能是分发触摸事件,该方法会根据它的返回值来决定事件要往哪个方向传递:
(1)若返回父类方法,则交由系统来决定,即为系统默认方向继续传递给上层控件的方法;
(2)若返回true,则不再继续传递,传递到这个方法为止,即这个方法消耗了事件;
(3)若返回false,事件传递到该方法后,事件退回到底下的父控件,继续往系统默认方向传递;如图8所示,MyTextView的dispatchTouchEvent返回false,事件退回到MyLinearLayout的onInterceptTouchEvent,继续往MyLinearLayout的onTouchEvent传递,即相当于以MyLinearLayout为最顶层控件;
三、关于onInterceptTouchEvent
1. MyLinearLayout的onInterceptTouchEvent返回true
总结:该方法只有ViewGroup的控件才会有,其父类的方法直接返回false,所以返回false就为图1中按默认方向传递了;该方法的作用就是拦截触摸事件,如果返回true,就是在当前控件拦截住触摸事件,传递给自己的onTouchEvent方法,再继续往底下控件传递给onTouchEvent,如果返回false或父类方法,就传递给它上面控件的dispatchTouchEvent;