1 概述
由于android系统是的控件是基于View和ViewGroup的树形结构,其控件嵌套后使用,故Activity,ViewGroup和View之间存在事件传递的问题。
事件传递涉及到三个方法:
- dispatchTouchEvent 事件的分发,通常是由上层(viewgroup)向下层(view)分发
- onInterceptTouchEvent 事件的拦截,阻止事件继续分发。此方法view是没有的,因为view已经是事件分发底层控件,故不需要拦截。
- onTouchEvent 事件的处理,通过是由下层向上层递归。
2 事件传递例子
这里我们涉及到activity、viewgroup和view三部分,首先我们自定义一个ViewGroup,简单的继承LinearLayout就好,重写其三个事件传递相关方法:
public class MyViewGroup extends LinearLayout {
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "MyLayout onTouchEvent " + Utils.getTouchAction(event));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("event", "MyLayout dispatchTouchEvent " + Utils.getTouchAction(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.d("event", "MyLayout onInterceptTouchEvent " + Utils.getTouchAction(event));
return super.onInterceptTouchEvent(event);
}
}
这里,Utils.getTouchAction 是用于获取点击事件的分类,如Down/Up。然后再自定义View,这里简单的继承TextView,重写两个事件传递相关方法,(view没有onInterceptTouchEvent方法)
public class MyView extends TextView{
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "MyView onTouchEvent " + Utils.getTouchAction(event));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("event", "MyView dispatchTouchEvent " + Utils.getTouchAction(event));
return super.dispatchTouchEvent(event);
}
}
最终,在activity中将以上两个控件使用起来:
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyViewGroup viewGroup = new MyViewGroup(this);
MyView view = new MyView(this);
viewGroup.addView(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setContentView(viewGroup);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "MyActivity onTouchEvent " + Utils.getTouchAction(event));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("event", "MyActivity dispatchTouchEvent " + Utils.getTouchAction(event));
return super.dispatchTouchEvent(event);
}
}
点击后的日志如下:
如上图,可知Touch的Down事件传递流程:
- 最先由activity拿到,经有activity的dispatchTouchEvent往下传递
- 接着收到该事件的是ViewGroup,同样ViewGroup的dispatchTouchEvent也往下传递
- ViewGroup往下传递的过程中,会调用到其onInterceptTouchEvent事件。若onInterceptTouchEvent返回false(默认返回false),则继续向view传递。
- 接着收到此事件的是View的dispatchTouchEvent,由于view已经是最底层的接收者,故这里不再往下传递
- 接着调用了View的onTouchEvent事件,由于view里面的onTouchEvent。
- 下面就是反过来往上传递给ViewGroup的onTouchEvent。
- 最终回到activity的OnTouchEvent,由于都没有处理,此Down事件就此结束。
接着手指拿起,开始响应Up事件
- UP事件同Down事件一样,首先由activity拿到,然后activity的dispatchTouchEvent分发。
- 由于之前的Down事件View、ViewGroup都没有处理(使用默认的返回false),故此时Up事件(其实还包括Moving事件等,下同)会直接跳过View和ViewGroup,直接传递给activity的OnTouchEvent。
3 事件传递机制分析和截断
在楼上的例子中,我们看了正常的一次事件传递流程,接下来我们就要考虑几个问题了,dispatchTouchEvent这个方法是用来分发事件的,必不可少;但是onInterceptTouchEvent用来干嘛的呢?我们将上面的例子改一下。
修改MyViewGroup文件的onInterceptTouchEvent,如下:
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.e("event", "MyLayout onInterceptTouchEvent " + Utils.getTouchAction(event));
//这里注释掉默认的回调(默认的是false)
//return super.onInterceptTouchEvent(event);
return true;
}
然后我们再看看运行的结果:
这里我们看到,ViewGroup的onInterceptTouchEvent事件返回true后,此事件就没有再往下传递给View了,包括响应的Up事件,都在该ViewGroup层停止了往下的传递。也就是说onInterceptTouchEvent事件阻止了onInterceptTouchEvent把事件继续往下传输,而是直接调用OnTouchEvent处理事件
同样,由于之前的Down事件View、ViewGroup都没有处理(使用默认的返回false),故Up事件会直接跳过View和ViewGroup,直接传递给activity的OnTouchEvent。
了解了onInterceptTouchEvent是用来截断事件传递的,我们在看看onTouchEvent,此方法是用于处理
事件的。事件的处理流程一般是由底层向上层传递的。我们看看onTouchEvent事件能否截断呢?
恢复ViewGroup的修改,然后修改MyView的OnTouchEvent方法,如下:
public boolean onTouchEvent(MotionEvent event) {
Log.e("event", "MyView onTouchEvent " + Utils.getTouchAction(event));
return true;
}
可以看到运行后的输出如下:
这里可以看到两点区别:
- 由于View处理了OnTouchEvent事件,故Up事件也会再次传递下来。
- 同样,由于View处理了OnTouchEvent事件且返回了true,表示消耗了此事件,则不会再往上层的OnTouchEvent传递。
4 总结
- 事件的分发是由上层往下层,通过dispatchTouchEvent方法分发,最先接收到事件的是activity,然后向ViewGroup分发,ViewGroup再向View分发。
- 事件的分发过程可以使用onInterceptTouchEvent中断,onInterceptTouchEvent返回true后,此事件将不再往下分发。
- 事件的处理流程和事件的分发是相反的,事件分发到最底层(View层)或者onInterceptTouchEvent截断层后,不再往下分发,而是调用该层的onTouchEvent方法,然后递归向上层回调onTouchEvent方法,知道activity。
- 某一层的onTouchEvent事件返回true的时候,表示消耗了此事件,则不再往上层回调其他onTouchEvent,此事件结束。
- 一个点击流程包括了Down Moving Up三类主要事件,在事件传递机制中,若某一底层未处理Down事件,则其后续的Moving Up事件不会再传递到该层。