Android Touch 简易流程分析

概述

流程分析由Activity开始,前面不分析。
触碰事件首先发放到Activity,Activity再转发给它依赖的Window(也就是PhoneWindow对象),Window转发给它所持有的DecorView(也就是顶层FrameLayout对象),由此开始就是经常说的ViewGroup与View的触碰事件传递流程了。

把上面的流程分两部分分析,第一部分是Activity到DecorView(简单);第二部分是ViewGroup与View流程(复杂)。

第一部分

Activity:

public boolean dispatchTrackballEvent(MotionEvent ev) {
        if (getWindow().superDispatchTrackballEvent(ev)) {
            return true;
        }
        return onTrackballEvent(ev);
    }

getWindow的实现对象是PhoneWindow。

PhoneWindow:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

mDecor是顶层布局DecorView对象,而DecorView对象继承自FrameLayout布局对象。
DecorView:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	...
	public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    ...
}

DecorView的superDispatchTouchEvent方法还是调用的super.dispatchTouchEvent,其实是调用到了ViewGroup中。有此处往后就进入了ViewGroup的分发流程了。

第二部分

对ViewGroup的dispatchTouchEvent方法进行分段分析。
ViewGroup:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    		第一段
    	    // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
    }

检测拦截状态。if判断条件actionMasked==MotionEvent.ACTION_DOWN表示是按下事件,mFirstTouchTarget != null表示有它的子控件消费了事件,即if(Down事件 或 Move/Up同时Down时有事件消费了它),这是获取ViewGroup的FLAG_DISALLOW_INTERCEPT标识,如果时true,表示它的子控件不希望父容器拦截事件,反之就调用onInterceptTouchEvent拦截判断方法。
最后一个判断条件是说Move/Up事件时没有子控件消费之前的Down事件,就设置拦截。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    		......
    		第二段	
            if (!canceled && !intercepted) {
                ......
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                }
            }
            ......
    }

只有当此次事件未被取消未被拦截的情况才进行第二段的代码,否则略过。
并且只有Down事件才会进入。
也就是未拦截情况下的Down事件,就会执行第二段。
接着for循环遍历所有子控件,将不符合条件或不在点击范围内的子控件跳过。剩下符合条件的事件会调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
ViewGroup:

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
            ......
           if (child == null) {
               handled = super.dispatchTouchEvent(transformedEvent);
           } else {
               handled = child.dispatchTouchEvent(transformedEvent);
           }
           ......
    }

其实就是根据参数child是否为空去调用父类或child的dispatchTouchEvent分发。

如果分发后有子控件消费了事件就会返回true,然后进入分支调用addTouchTarget(child, idBitsToAssign)方法
ViewGroup:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

这里会先用子控件child构造一个TouchTarget对象,TouchTarget是一个链表结构,下面的操作是将mFirstTouchTarget链接到新构造的TouchTarget后面,并将mFirstTouchTarget指向新构造的TouchTarget。

ViewGroup:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    	第三部分
    	    if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
    }

mFirstTouchTarget == null说明没有子控件消费事件,此时调用dispatchTransformedTouchEvent(ev, canceled, null, ALL_POINTER_IDS),注意此处的chid参数是null,也就是调用ViewGroup的super.dispatchTouchEvent,最终调用View的dispatchTouchEvent,而View的此方法里会调用View的onTouchEvent方法。由前面的分析知道,只有Down事件才能进入第二段的判断,而mFirstTouchTarget是在第二段代码里Down事件消费了才会赋值的,也就是说没有消费Down事件的后序事件会进入此处的判断里,简而言之,Down事件没有人消费的话,后面的MOVE/UP事件在顶层DecorView里就会被打回去,不会向下分发。总结:处理了未消费的Down事件和后序的MOVE/UP事件。

mFirstTouchTarget != null说明有子控件消费Down事件,接着循环mFirstTouchTarget链表,在循环里有个判断,if(alreadyDispatchedToNewTouchTarget && target == newTouchTarget)是说明此次是Down事件并且事件被消费了,直接设置dispatchTouchEvent返回值为true;else的情况就是Down被消费后的MOVE/UP事件,这里又调用了dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)并且child参数是target.child(即消费了Down事件的子控件),意思是把MOVE/UP直接交给了消费了Down事件的子控件去了。总结:处理了消费了的Down 事件(即不做什么处理)和后序的MOVE/UP事件(直接交给消费Down事件的子控件处理)。

总结

ViewGroup的分发分为上面的三步走,暂不考虑子控件主动设置FLAG_DISALLOW_INTERCEPT。
第一步,设置intercepted,分成两份,一份是Down事件和消费Down事件的后序事件MOVE/UP,执行onInterceptTouchEvent方法设置intercepted;一份是未消费Down事件的后序事件MOVE/UP,intercepted=false。
第二步,只有未拦截的Down事件才能进入这部分,遍历所有子控件找到符合的子控件调用child.dispatchTouchEvent。这是一个不断迭代的过程,ViewGroup不断迭代子ViewGroup的dispatchTouchEvent方法,直到有控件消费了Down事件,这时就在ViewGroup里把这个子控件保存起来。
第三步,根据第二步保存的子控件链表mFirstTouchTarget是否null,分成两部分,
**(1)**是==null,未消费的Down事件或Down未消费的后序事件MOVE/UP,这时调用ViewGroup的super.dispatchTouchEvent方法。
**(2)**是!=null,消费的Down事件或Down消费的后序事件MOVE/UP,这时Down事件直接设置方法返回值true,MOVE/UP事件就去找到消费Down事件的子控件,然后调用子控件的dispatchTouchEvent方法。

其实总是说Android的触碰事件在View树里的传递流程是一个U字型,由左边用dispatchTouchEvent不断往下分发,再由右边用onTouchEvent不断往上输送,上面代码第二段负责左边,第三段负责右边。

你可能感兴趣的:(android)