ViewGroup的事件分发机制

来源:互联网 发布:mysql 数据库log文件 编辑:程序博客网 时间:2024/05/20 00:16

上一次我在View的事件分发机制里完整的分析了View对于触屏点击事件的分发过程,接下来继续探索之旅,紧接着分析ViewGroup的事件分发机制,ViewGroup其实就是一组View的集合,它也是继承于View的,它本身也可以包含ViewViewGroup,方便起见我们还是延用上一次的布局,不过这一次我们给根布局也设置了点击事件和触摸事件:

public class MainActivity extends Activity implements OnClickListener,OnTouchListener{    private RelativeLayout re_Layout;    private Button btn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn=(Button) findViewById(R.id.btn);        re_Layout=(RelativeLayout) findViewById(R.id.re_layout);        btn.setOnClickListener(this);        btn.setOnTouchListener(this);        re_Layout.setOnClickListener(this);        re_Layout.setOnTouchListener(this);    }    @Override    public void onClick(View v) {        Log.d("TAG", "OnClick--"+v);    }    @Override    public boolean onTouch(View v, MotionEvent event) {        Log.d("TAG", "onTouch--"+event.getAction()+"--"+v);        return false;    }}

这里写图片描述

效果图也依旧没有变,现在我们先点击一下Button,查看Log输出:
这里写图片描述

可以很清楚的看到这里和上一节分析得情况一样:当点击事件发生时onTouch()方法是优先于onClick()方法执行的,并且如果onTouch()返回False既不消耗点击事件那么如果控件设置了setOnClickListener最终是会执行到onClick()方法的,可是我很好奇ViewGroup的点击事件和View的到底有什么区别,点击事件事件的分发到底是从ViewGroup开始还是从View开始的呢,于是我点击了Button以外的空白区域,捕捉到如下信息(注:我的根布局就是RelativeLayout):
这里写图片描述

说明根布局也就是Viewroup也是可以响应点击事件的,但是我们点击View的时候为什么没有ViewGroup的Log输出,这是不是说明android事件分发是先传到View的,当View消耗的这个事件它的ViewGroup就无法接收这个事件了呢,为了彻底的谈清楚原因,我们先重写一个ViewGroup,然后重写这个ViewGroup里面的onInterceptTouchEvent(MotionEvent ev)dispatchTouchEvent(MotionEvent event)还有onTouchEvent(MotionEvent event)这三个方法通过Log输出信息来判断:

public class MyLayout extends RelativeLayout{    public MyLayout(Context context,AttributeSet attrs) {        super(context,attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.d("TAG", ev.getAction()+" action"+"MyLayout onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("TAG", event.getAction()+" action"+"MyLayout onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("TAG", ev.getAction()+" action"+"MyLayout dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }

同理我们还需要重写一个Button:

public class MyButton extends Button{    public MyButton(Context context,AttributeSet attrs){        super(context,attrs);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("TAG", event.getAction()+" action"+"MyButton onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("TAG", ev.getAction()+" action"+"MyButton dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    //这里注意View是没有onInterceptTouchEvent方法的

效果图是一样的这里就不再贴了,为了验证刚才的想法我们直接点击一下界面上的Button,Log输出如下:
这里写图片描述

发现了什么,我们点击的是Button,然而这个事件最开始是传到了我们的根布局MyLayout,并且还按照:
dispatchTouchEventonInterceptTouchEventdispatchTouchEvent的顺序执行,紧接着执行ViewonTouch()onTouchEvent()方法,还有一点很奇怪的事只有最开始ACTION_DOWN的时候调用了ViewGroup的onInterceptTouchEvent方法,在后面的ACTION_UP事件派发过程中却没有调用,这里给出一个合理的猜想:一旦一个View开始处理这个触摸事件,那么接下来的ACTION_MOVE和ACTION_UP事件都会交给它去处理,就好比你在公司里面做事,分到你做的事你已经做了一些,那么接下来的事你的完完整整的做好,那么如果做到一半不做了会怎么样(即View不消耗ACTION_DOWN事件)?我们可以大胆的假设如果上级交给你做的事没有做好,那么上级_在短期内肯定不敢交代事情给你做了(后续的ACTION_MOVE、ACTION_DOWN事件这个View都接收不到了),那么究竟如何我们还是从源码看起。

我们已经知道当一个点击操作发生时事件是先传给ViewGroup处理的并且首先执行的是ViewGroupdispatchTouchEvent,那么我们就先来看看它的源码:

 /**     * {@inheritDoc}     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {            ev.setTargetAccessibilityFocus(false);        }        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }            // 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 intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }            // Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;            // Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.                        // Scan children from front to back.                        final ArrayList<View> preorderedList = buildOrderedChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder                                    ? getChildDrawingOrder(childrenCount, i) : i;                            final View child = (preorderedList == null)                                    ? children[childIndex] : preorderedList.get(childIndex);                            // If there is a view that has accessibility focus we want it                            // to get the event first and if not handled we will perform a                            // normal dispatch. We may do a double iteration but this is                            // safer given the timeframe.                            if (childWithAccessibilityFocus != null) {                                if (childWithAccessibilityFocus != child) {                                    continue;                                }                                childWithAccessibilityFocus = null;                                i = childrenCount - 1;                            }                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }                            newTouchTarget = getTouchTarget(child);                            if (newTouchTarget != null) {                                // Child is already receiving touch within its bounds.                                // Give it the new pointer in addition to the ones it is handling.                                newTouchTarget.pointerIdBits |= idBitsToAssign;                                break;                            }                            resetCancelNextUpFlag(child);                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {                                            mLastTouchDownIndex = j;                                            break;                                        }                                    }                                } else {                                    mLastTouchDownIndex = childIndex;                                }                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                            // The accessibility focus didn't handle the event, so clear                            // the flag and do a normal dispatch to all children.                            ev.setTargetAccessibilityFocus(false);                        }                        if (preorderedList != null) preorderedList.clear();                    }                    if (newTouchTarget == null && mFirstTouchTarget != null) {                        // Did not find a child to receive the event.                        // Assign the pointer to the least recently added target.                        newTouchTarget = mFirstTouchTarget;                        while (newTouchTarget.next != null) {                            newTouchTarget = newTouchTarget.next;                        }                        newTouchTarget.pointerIdBits |= idBitsToAssign;                    }                }            }            // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                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;                }            }            // Update list of touch targets for pointer up or cancel, if needed.            if (canceled                    || actionMasked == MotionEvent.ACTION_UP                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                resetTouchState();            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {                final int actionIndex = ev.getActionIndex();                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);                removePointersFromTouchTargets(idBitsToRemove);            }        }        if (!handled && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);        }        return handled;    }

相当长,还是一点点来看,源码这种东西看不懂肯定会觉得很枯燥,所以能弄懂的尽量弄懂,最开始只是一些对View是否可以获得焦点的判断、设置标志位以及初始化一些布尔值,并且在ACTION_DOWN事件产生的时候清楚以外的状态并且准备开始新一轮的手势操作,不重要:

     if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {            ev.setTargetAccessibilityFocus(false);        }        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }

先看这一段:

  // 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;            }

这里首先设立了一个布尔值interception去判断当前ViewGroup是否要拦截View的点击事件,if条件语句中的内容是当产生ACTION_DOWN按下事件或者mFirstTouchTarget != null的时候去判断是否要拦截当前事件,这里主要关注mFirstTouchTarget != null这个点,我们找一找哪个方法跟这个mFirstTouchTarget变量有关,还真给我找到了,看下面:

 private void clearTouchTargets() {        TouchTarget target = mFirstTouchTarget;        if (target != null) {            do {                TouchTarget next = target.next;                target.recycle();                target = next;            } while (target != null);            mFirstTouchTarget = null;        }    }
/**     * Cancels and clears all touch targets.     */    private void cancelAndClearTouchTargets(MotionEvent event) {        if (mFirstTouchTarget != null) {            boolean syntheticEvent = false;            if (event == null) {                final long now = SystemClock.uptimeMillis();                event = MotionEvent.obtain(now, now,                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);                syntheticEvent = true;            }            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {                resetCancelNextUpFlag(target.child);                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);            }            clearTouchTargets();            if (syntheticEvent) {                event.recycle();            }        }    }

这两段代码结合起来,在加上在ACTION_DOWN初始时候是调用了cancelAndClearTouchTargets(MotionEvent event)这个方法的,所以我们可以推荐起初这个mFirstTouchTarget 的值是null的,那么mFirstTouchTarget是在哪里赋值的呢,我们在dispatchTouchEvent(MotionEvent ev)接着往下看:

    newTouchTarget = addTouchTarget(child, idBitsToAssign);

我们看到newTouchTarget是在这里赋值的,看一下addTouchTarget方法:

 /**     * Adds a touch target for specified child to the beginning of the list.     * Assumes the target child is not already present.     */    private TouchTarget addTouchTarget(View child, int pointerIdBits) {        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);        target.next = mFirstTouchTarget;        mFirstTouchTarget = target;        return target;    }

从该方法的内部结构可以看出,mFirstTouchTarget其实是一中单链表结构,如果找到了处理该点击事件的子View那么mFirstTouchTarget就会被赋值并且会指向子元素。
这一下弄清楚了回到刚才的那段代码:

  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;            }
 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

这个布尔值是判断子元素是否调用了requestDisallowInterceptTouchEvent这个方法,如果调用了这个布尔值就为True,这里看一眼这个方法的代码:

 /**     * {@inheritDoc}     */    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {            // We're already in this state, assume our ancestors are too            return;        }        if (disallowIntercept) {            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;        } else {            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // Pass it up to our parent        if (mParent != null) {            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);        }    }

你可以在子View中调用这个方法来让ViewGroup不拦截除了ACTION_DOWN以外的点击事件,这里为什么说是ACTION_DOWN以外呢,因为ViewGroup在分发事件的时候最开始是会重置FLAG_DISALLOW_INTERCEPT这个标志位的,所以无论你有没有在子View中设置requestDisallowInterceptTouchEvent方法都不会影响到ViewGroup去拦截ACTION_DOWN事件的,接着往下看:

  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;            }

如果子View没有设置requestDisallowInterceptTouchEvent方法那么就调用ViewGrouponInterceptTouchEvent(ev)方法,我们找到这个方法:

  /**     * Implement this method to intercept all touch screen motion events.  This     * allows you to watch events as they are dispatched to your children, and     * take ownership of the current gesture at any point.     *     * <p>Using this function takes some care, as it has a fairly complicated     * interaction with {@link View#onTouchEvent(MotionEvent)     * View.onTouchEvent(MotionEvent)}, and using it requires implementing     * that method as well as this one in the correct way.  Events will be     * received in the following order:     *     * <ol>     * <li> You will receive the down event here.     * <li> The down event will be handled either by a child of this view     * group, or given to your own onTouchEvent() method to handle; this means     * you should implement onTouchEvent() to return true, so you will     * continue to see the rest of the gesture (instead of looking for     * a parent view to handle it).  Also, by returning true from     * onTouchEvent(), you will not receive any following     * events in onInterceptTouchEvent() and all touch processing must     * happen in onTouchEvent() like normal.     * <li> For as long as you return false from this function, each following     * event (up to and including the final up) will be delivered first here     * and then to the target's onTouchEvent().     * <li> If you return true from here, you will not receive any     * following events: the target view will receive the same event but     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further     * events will be delivered to your onTouchEvent() method and no longer     * appear here.     * </ol>     *     * @param ev The motion event being dispatched down the hierarchy.     * @return Return true to steal motion events from the children and have     * them dispatched to this ViewGroup through onTouchEvent().     * The current target will receive an ACTION_CANCEL event, and no further     * messages will be delivered here.     */    public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;    }

唔,注释相当长,但是有用的就一个返回值,这里返回False,说明ViewGrup不拦截点击事件,事件可以继续往下传递,这个方法的最后,如果当前界面除了一个ViewGroup没有任何子View,那么此时ViewGroup也会拦截点击事件,就好比一个公司人手不够,公司领导需要亲力亲为一样。
接着:

// If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }

如果确定拦截或者已经有子View着手处理这个点击事件,那么就开始正常的事件分发流程。

 final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;

这里是通过标志位和ACTION_CANCLE来检查是否cancle,再下去:

 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

首先可以看见获取一个boolean变量标记split来标记,默认是true,作用是是否把事件分发给多个子View,这个同样在ViewGroup中提供了public的方法设置,如下:

public void setMotionEventSplittingEnabled(boolean split) {        // TODO Applications really shouldn't change this setting mid-touch event,        // but perhaps this should handle that case and send ACTION_CANCELs to any child views        // with gestures in progress when this is changed.        if (split) {            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;        } else {            mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;        }    }

这一段摘自:Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)  

    if (!canceled && !intercepted) {

如果没有取消当前动作并且ViewGroup未拦截事件那么事件就传递到接收了该点击事件的View,接下来是一大段代码预警:

 // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.                        // Scan children from front to back.                        final ArrayList<View> preorderedList = buildOrderedChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder                                    ? getChildDrawingOrder(childrenCount, i) : i;                            final View child = (preorderedList == null)                                    ? children[childIndex] : preorderedList.get(childIndex);                            // If there is a view that has accessibility focus we want it                            // to get the event first and if not handled we will perform a                            // normal dispatch. We may do a double iteration but this is                            // safer given the timeframe.                            if (childWithAccessibilityFocus != null) {                                if (childWithAccessibilityFocus != child) {                                    continue;                                }                                childWithAccessibilityFocus = null;                                i = childrenCount - 1;                            }                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }                            newTouchTarget = getTouchTarget(child);                            if (newTouchTarget != null) {                                // Child is already receiving touch within its bounds.                                // Give it the new pointer in addition to the ones it is handling.                                newTouchTarget.pointerIdBits |= idBitsToAssign;                                break;                            }                            resetCancelNextUpFlag(child);                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {                                            mLastTouchDownIndex = j;                                            break;                                        }                                    }                                } else {                                    mLastTouchDownIndex = childIndex;                                }                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }

嗯,这段代码的逻辑比较清晰,大体上就是遍历ViewGroup的所有子元素,然后判断子元素是否能够接收到点击事件,接收的依据有两种:第一种是判断子元素是否在播放动画,第二种是判断点击事件的坐标是否落在子元素的区域内,从这里可以看出来:

if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }                            newTouchTarget = getTouchTarget(child);                            if (newTouchTarget != null) {                                // Child is already receiving touch within its bounds.                                // Give it the new pointer in addition to the ones it is handling.                                newTouchTarget.pointerIdBits |= idBitsToAssign;                                break;                            }

如果子元素满足了这两个条件,点击事件就会交给它处理,接下来这段代码里面有一个很重要的方法:

       if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {                                            mLastTouchDownIndex = j;                                            break;                                        }                                    }                                } else {                                    mLastTouchDownIndex = childIndex;                                }                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }

这里if判断里面这个dispatchTransformedTouchEvent是将Touch事件传递给特定的子View,它实际上在内部是调用了子元素的disPatchTouchEvent方法,找一下它的源码:

    /**     * Transforms a motion event into the coordinate space of a particular child view,     * filters out irrelevant pointer ids, and overrides its action if necessary.     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.     */    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,            View child, int desiredPointerIdBits) {        final boolean handled;        // Canceling motions is a special case.  We don't need to perform any transformations        // or filtering.  The important part is the action, not the contents.        final int oldAction = event.getAction();        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {            event.setAction(MotionEvent.ACTION_CANCEL);            if (child == null) {                handled = super.dispatchTouchEvent(event);            } else {                handled = child.dispatchTouchEvent(event);            }            event.setAction(oldAction);            return handled;        }        // Calculate the number of pointers to deliver.        final int oldPointerIdBits = event.getPointerIdBits();        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;        // If for some reason we ended up in an inconsistent state where it looks like we        // might produce a motion event with no pointers in it, then drop the event.        if (newPointerIdBits == 0) {            return false;        }

看这一段内容:

  if (child == null) {                handled = super.dispatchTouchEvent(event);            } else {                handled = child.dispatchTouchEvent(event);            }

如果传递的child为null就调用父类的dispatchTouchEvent否则就调用子类的dispatchTouchEvent,而上面的代码中child不为null,所以执行子元素的dispatchTouchEvent,如果子元素的dispatchTouchEvent返回的是True,那么含有dispatchTransformedTouchEvent这个方法内部的for循环就不会继续下去,直接跳到这里:

 mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;

这个地方前面说过是给mFirstTouchTarget赋值的地方,如果情况改变,前面那个dispatchTransformedTouchEvent方法中child返回的是False,那么如果当前ViewGroup会把点击事件传递给下一个子元素进行处理,执行for循环查找下一个子元素,此时mFirstTouchTarget依然未被赋值为null,那么这时候继续查看接下来的代码:

     if (newTouchTarget == null && mFirstTouchTarget != null) {                        // Did not find a child to receive the event.                        // Assign the pointer to the least recently added target.                        newTouchTarget = mFirstTouchTarget;                        while (newTouchTarget.next != null) {                            newTouchTarget = newTouchTarget.next;                        }                        newTouchTarget.pointerIdBits |= idBitsToAssign;                    }

这一段代码表示当前没有找到可以接收点击事件的View并且我们的mFirstTouchTarget!=null那么就把最开始的TouchTarget赋值给newTouchTarget,最后:

 // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);

执行到这里的话有两种情况,一种是ViewGroup里面没有找到子View,另一种就是找到了处理这次点击事件的子View但是这个子View的disPatchTouchEvent返回了False,我们通过前面的分析知道disPatchTouchEvent中是先执行onTouch()方法的,而一般onTouch()方法返回的是False,此时disPatchTouchEvent方法的返回值由onTouchEvent方法决定,出现这种情况说明onTouchEvent返回了False,在以上两种情况下,ViewGroup会自己处理这个点击事件,注意这里这个方法里的child传入的是null,我们前面就知道了传入null会执行handled = super.dispatchTouchEvent(event);也就是说此时交由ViewGroup处理这个事件。而ViewGroup也是View的子类,它里面是没有重写ViewonTouchEvent方法的,所以它自身处理点击事件的流程和我们在View的事件分发机制解析里面分析得是一样的,至此ViewGroup的分发事件分析完毕。

这次我们只分析了点击Button时的Log输出,下面给出点击空白处的Log输出,可以自己检验一下分析成果:
这里写图片描述

一些结论:

  • ViewGroup默认不拦截任何事件,Android源码中ViewGrouponInterceptTouchEvent方法默认返回false
  • ViewonTouchEvent默认都会消耗事件(返回true),除非是不可点击的(clickablelongClicjable同时为false),ViewlongClickable属性默认是false
  • Viewenable属性不会影响onTouchEvent的返回值,哪怕该Viewdisable的,只要它的clickablelongClickable其中一个为true,那么它的onTouchEvent就返回true
  • 事件传递过程是由外向内的,即事件总是先传给父元素,然后再由父元素分发给子VIew,子View可以通过requestDisallowInterceptTouchEvent来干预父元素的分发过程,但是影响不到ACTION_DOWN事件

参考:
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

写东西很辛苦,如果觉得写的还看得过去的话请点个赞吧。

1 0
原创粉丝点击