天天看点

Android事件处理分发机制的总结:二(事件分发)

之前我们知道触摸事件是被包装成MotionEvent进行传递的,而该对象是继承了Parcelable接口,正因为如此,才可以从系统中传递到我们的应用中。系统通过AIDL跨进程调用了应用的Activity的dispatchTouchEvent方法,并把MotionEvent对象作为参数传递过来。

dispatchTouchEvent就是触摸事件传递的对外接口,无论是系统传给Activity,还是Activity传递给ViewGroup,ViewGroup传递给子View,都是直接调用对方的dispatchTouchEvent方法,并传递MotionEvent参数。

我们首先来看看Activity中的dispatchTouchEvent逻辑:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();    
        //这是一个空实现的方法,以便子类实现,该方法在Key事件和touch事件的dispatch方法中都被调用,就是方便用户在事件被传递之前做一下自己的处理。
    }
    //这才是事件真正的分发
    if (getWindow().superDispatchTouchEvent(ev)) {
        //superDispatchTouchEvent是一个抽象方法,但是getWindow()获取的对象实际是FrameWork层的PhoneWindow,该对象实现了这个方法,内部是直接调用DecorView的superDispatchTouchEvent是直接调用dispatchTouchEvent,这样就传递到子View中了   
        return true;
    }
    //如果上面事件没有被消费掉,那么就调用Activity的onTouchEvent事件。
    return onTouchEvent(ev);
}

//PhoneWindow的superDispatchTouchEvent方法直接调用了mDecor的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

//mDecor即为Activity真正的根View,我们通过setContentView所添加的内容就是添加在该View上,它实际上就是一个FrameLayout
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);//FrameLayout.dispatchTouchEvent
}
           

至此我们已经至少明白了以下几点:

1、我们可以重载Activity的onUserInteraction方法,在Down事件触发传递前,实现我们的一些需求,实际上源码中有很多这样的方法,再某个方法体的第一行提供一个空实现的回调方法,在某个方法的最后一行提供一个空实现的回调方法,以便子类去实现自己的逻辑,例如AsyncTask就有类似的方式。这些技巧都能很好的提高我们代码的扩展性。

2、Activity会间接的调用根View的dispatchTouchEvent,并通过if判断返回值,如果为true,即向上层返回true,也就是调用Activity的dispatchTouchEvent的WMS,即操作系统。

3、如果if判断为false,即根View和根View下的所有子View均为消费掉该事件,那么下面的代码就有执行机会,即Activity的onTouchEvent,并把该方法的返回值作为结果返回给上层。

接下来我们来研究ViewGroup的dispatchTouchEvent,这是稍微复杂的分发逻辑,上面的根View也就是一个ViewGroup。

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();//获取事件
    final float xf = ev.getX();//获取触摸坐标
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;//获取当前需要偏移的偏移量量
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;    //当前ViewGroup的视图矩阵

    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != ;//是否禁止拦截

    if (action == MotionEvent.ACTION_DOWN) {//如果事件是按下事件
        if (mMotionTarget != null) {    //判断接受事件的target是否为空
            //不为空肯定是不正常的,因为一个事件是由DOWN开始的,而DOWN还没有被消费,所以目标也不是不可能被确定,
            //造成这个的原因可能是在上一次up事件或者cancel事件的时候,没有把目标赋值为空
            mMotionTarget = null;    //在此处挽救
        }
        //不允许拦截,或者onInterceptTouchEvent返回false,也就是不拦截。注意,这个判断都是在DOWN事件中判断
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            //从新设置一下事件为DOWN事件,其实没有必要,这只是一种保护错误,防止被篡改了
            ev.setAction(MotionEvent.ACTION_DOWN);
            //开始寻找能响应该事件的子View
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - ; i >= ; i--) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {//如果child可见,或者有动画,获取该child的矩阵
                    child.getHitRect(frame);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        // 设置系统坐标
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        if (child.dispatchTouchEvent(ev))  {//调用child的dispatchTouchEvent
                            //如果消费了,目标就确定了,以便接下来的事件都传递给child
                            mMotionTarget = child;
                            return true;    //事件消费了,返回true
                        }
                    }
                }
            }
            //能到这里来,证明所有的子View都没消费掉Down事件,那么留给下面的逻辑进行处理
        }
    }
    //判断是不是up或者cancel事件
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);

    if (isUpOrCancel) {
        //如果是取消,把禁止拦截这个标志位给取消
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; 
    }


    final View target = mMotionTarget;
    if (target == null) {
    //判断该值是否为空,如果为空,则没找到能响应的子View,那么直接调用super的dispatchTouchEvent,也就是View的dispatchTouchEvent
        ev.setLocation(xf, yf);
        return super.dispatchTouchEvent(ev);
    }

    //能走到这里来,说明已经有target,那也说明,这里不是DOWN事件,因为DOWN事件如果有target,已经在前面返回了,执行不到这里
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {//如果有目标,又非要拦截,则给目标发送一个cancel事件
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setAction(MotionEvent.ACTION_CANCEL);//该为cancel
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
            //调用子View的dispatchTouchEvent,就算它没有消费这个cancel事件,我们也无能为力了。
        }
        //清除目标
        mMotionTarget = null;
        //有目标,又拦截,自身也享受不了了,因为一个事件应该由一个View去完成
        return true;//直接返回true,以完成这次事件,好让系统开始派发下一次
    }

    if (isUpOrCancel) {//取消或者UP的话,把目标赋值为空,以便下一次DOWN能重新找,此处就算不赋值,下一次DOWN也会先把它赋值为空
        mMotionTarget = null;
    }
    //又不拦截,又有目标,那么就直接调用目标的dispatchTouchEvent
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);

    return target.dispatchTouchEvent(ev);
    //也就是说,如果是DOWN事件,拦截了,那么每次一次MOVE或者UP都不会再判断是否拦截,直接调用super的dispatchTouchEvent
    //如果DOWN没拦截,就是有其他View处理了DOWN事件,那么接下来的MOVE或者UP事件拦截了,那么给目标View发送一个cancel事件,告诉它touch被取消了,并且自身也不会处理,直接返回true
    //这是为了不违背一个Touch事件只能由一个View处理的原则。
}
再来看看View中的dispatchTouchEvent是如何分发事件的:
public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        //判断mOnTouchListener是否存在,并且控件可点的情况下,执行onTouch,如果onTouch返回true,就消耗该事件
        return true;
    }
    //如果以上条件都不成立,则把事件交给onTouchEvent来处理
    return onTouchEvent(event);
}
           

View中的处理相当简单明了,因为不涉及到子View,所以只在自身内部进行分发。

首先判断是否设置了触摸监听,并且可以响应事件,就交由监听的onTouch处理。

如果上述条件不成立,或者监听的onTouch事件没有消费掉该事件,则交由onTouchEvent进行处理,并把返回结果交给上层。

继续阅读