ViewRootImpl源码分析事件分发
当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件;若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MOVE的触摸事件,系统中预定义的用来判断用户手指在屏幕上的滑动是否是一个ACTION_MOVE动作的这个距离常量叫做TouchSlop,可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取。
1 onTouchEvent与onTouch
View.java(基于android2.3.3):
public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {//此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法
return true;
}
if (onTouchEvent(event)) {// onTouchEvent参考下面源码
return true;
}
public boolean onTouchEvent(MotionEvent event) {
...
// 当前onTouch的组件必须是可点击的比如Button,ImageButton等等,此处CLICKABLE为true,才会进入if方法,最后返回true。
// 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false,最后返回false。当然会有特殊情况,如果给这些View设置了onClick监听器,此处CLICKABLE也将为true,参考下面源码
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法,源码下面源码
}
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public boolean performClick() {
...
if (li != null && li.mOnClickListener != null) {
...
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
总结:
只有我们注册OnTouchListener时重写的onTouch()方法中返回false ——> 执行onTouchEvent方法 ——> 导致onClick()回调方法执行
onTouch()方法返回true ——> onTouchEvent方法不执行 ——> 导致onClick()回调方法不会执行
2 ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//onInterceptTouchEvent返回false,说明向下传递
//onInterceptTouchEvent返回true,说明拦截
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
...
// 伪代码如下:
//1,找到当前控件子控件
//2,判断当前touch的点的坐标(x,y)在哪个子控件的矩形区域内
//3,判断当前子控件是viewgroup的子类对象,还是view的子类对象
//3.1 如果是viewgroup的子类: 调用其dispatchTouchEvent方法,上述操作再来一遍
//3.2 view 尝试让当前view去处理这个事件(
true,dispatchTouchEvent方法结束,并且返回true
false,dispatchTouchEvent继续向下执行)
...
}
}
...
target = mMotionTarget
//target一定是null
if (target == null) {
...
//调用当前viewgroup的父View的处理事件的方法
return super.dispatchTouchEvent(ev);
}
...
}
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//处理down事件,主要目的是寻找target
if (action == MotionEvent.ACTION_DOWN) {
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
//找到了target
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
//如果允许对后续事件进行拦截,并且拦截成功的话
//通过这段代码可以知道,在childView中可以调用
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
mMotionTarget = null;
return true;
}
//后续事件:ACTION_MOVE系列事件和up事件都交给target处理
return target.dispatchTouchEvent(ev);
}
处理拦截事件有两种方式:
1 直接在父类中重写onInterceptTouchEvent(),让其返回true或false;
2 在子类中调用父类的requestDisallowInterceptTouchEvent(),让父类决定是否拦截。
上图是view事件分发的整个流程,注意Decorview的调用了两次。
1、Android中,Touch事件的分发分服务端和应用端。在Server端由WindowManagerService负责采集和分发的,在client端则是由ViewRootImpl(内部有个mView,也即Decorview ,负责控制View树的UI绘制和事件消息的分发)负责分发的。
2、WMS在启动之后,经过逐层调用,会在native层启动两个线程:InputReaderThread和InputDispatchThread,前者用来读取输入事件,
后者用来分发输入事件,输入事件经过nativie层的层层传递,最终会传递到Java层的ViewRootImpl中,调用
ViewPostImeInputStage(ViewRootImpl的内部类)中的各个方法来分发不同的事件,而Touch事件是通过processPointerEvent方法进行分发的。
3、processPointerEvent方法中调用mView.dispatchPointerEvent(event)方法,这里的mView就是DecorView,而dispatchPointerEvent方法会对event进行判断,如果是Touch事件的话,就调DecorViewd的dispatchTouchEvent进行事件分发,而Decorview的dispatchTouchEvent重写了,其内部调用了CallBack接口的dispatchTouchEvent方法,因为Activity实现了CallBack接口,所以实际调用的是Activity的dispatchTouchEvent方法,Activity的dispatchTouchEvent方法内部调用phongwindow的superDispatchTouchEvent方法,这个方法实际上调用了DecorView的superDispatchTouchEvent方法,因此到目前位置DecorView被调用了两次。然后DecorView的superDispatchTouchEvent方法中调用了ViewGroup的dispatchTouchEvent方法正式进行View树事件的派发
1一定要记住dispatchTouchEvent是所有View 的Touch事件入口,在它里面调用了onInterceptTouchEvent和OnTouchEvent两个方法以及被包含类的dispatchTouchEvent方法(如果此View是布局类的话),相当于责任链模式,dispatchTouchEvent对MotionEvent进行一级级的分发。
当View的dispatchTouchEvent方法返回true时,表示此View成功处理了这次事件,而具体由谁处理的,可能是此View的某个被包含类(如果有的话)处理的(譬如某个被包含类的dispatchTouchEvent返回true),也可能就是此View本身处理的(譬如此View的OnTouchEvent返回true)。
一个LinearLayout中有一个TextView,当触摸这个TextView时触摸事件会先打到LinearLayout,然后再到达TextView。如果LinearLayout将触摸事件拦截了,那么TextView就会收到一个CANCEL事件,其他触摸就收不到了。
如果ViewGroup的onInterceptTouchEvent方法中返回false,每个后续的事件(从当前事件到最后ACTION_UP事件)将会先分发到onInterceptTouchEvent中,然后再交给目标子控件的onTouchEvent处理 (前提是子控件的onTouchEvent返回是true )。
如果ViewGroup的onInterceptTouchEvent返回true,onInterceptTouchEvent方法中将不会收到后续的任何事件,也就是跳过onInterceptTouchEvent方法,目标子控件中除了ACTION_CANCEL外也不会接收所有这些后续事件,所有的后续事件将会直接被交付到ViewGroup的onTouchEvent()方法中。