Android事件分发机制(二)

上一篇讲了Android事件分发的基本概念,这一篇就接着上一篇事件分发接着写后面的故事。
首先先回顾一下上一篇的内容
上一篇主要讲了Android事件分发的三个重要方法以及Activity对事件分发,以及这三个方法之间的关系在这里就复习一下Activity对事件分发的过程
1.首先,当一个事件产生后,首先会传递Activity中。
2.当该事件传递到Activity之后会调用dispatchTouchEvent方法,如果当前Activity不拦截该事件,则会调用他的子View中的dispatchTouchEvent方法,以此循环直到子View拦截该事件。
3.如果当前Activity拦截了此事件,则会进行判断如果OnTouchListener被设置,则onTouch会被调用,否则OnTouchEvent会被调用,如果在OnTouchEvent中设置OnlickListener则onClick会被调用,从这也说明了OnlickListener的优先级最低,而OnTouchListener的优先级则高于OnTouchEvent

一、ViewGroup对事件分发过程

  • 事件传递过程
    Activity–>window–>顶级View
    当事件传递到顶级View是会调用顶级View的dispatchTouchEvent方法,在dispatchTouchEvent中会在如下两种情况下会判断是否要拦截当前事件,下面是View判断是否拦截当前事件的源码
   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;
            }

解释一下上面的源码
1、
首先会判断当前的事件类型为MotionEvent.ACTION_DOWN或者mFirstTouchTarget != null。当前的事件类型为MotionEvent.ACTION_DOWN很好理解那么mFirstTouchTarget != null是什么呢,其实mFirstTouchTarget 是在ViewGroup不拦截事件将事件交给子View处理时会给mFirstTouchTarget 赋值。

2、
如果 (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)的条件返回false的时候ViewGroup中的onInterceptTouchEvent(ev)方法将不调用,也就代表默认由当前ViewGroup处理该事件。

上面就是在dispatchTouchEvent中判断是否要拦截当前事件的两种情况。

二.ViewGroup如何将事件向下传递到子View中
如果ViewGroup不拦截当前事件,交给子View处理该事件,则会调用下面的代码。

              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 = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, 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();
                    }

上面的代码虽然多一点,但是逻辑确非常清晰,首先遍历ViewGroup的所有子元素,判断子元素是否能够接收点击事件,如果子元素能够接收点击事件,则会调用子元素的dispatchTouchEvent方法。这样事件就会交给子元素处理。从而完成一轮事件分发。
如果ViewGroup没有子元素或者子元素处理了点击事件,在这两中情况下ViewGroup就会自己处理点击事件。

二、View对点击事件的处理
View的事件分发(这里不包含ViewGroup)和ViewGroup差不多,但是要比ViewGroup还要简单很多,因为他没有子元素向下将事件传递下去
同样当一个事件产生以后他的传递过程也是 Activity–>window–>顶级View,当传递到View中是会调用VIew的dispatchTouchEvent方法,下面是View的dispatchTouchEvent方法的源码

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;

    ...
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

    ...      
     return result;
    }

这里我省略了一些源码只是将重要部分的代码展示出来,首先他会判断有没有设置onTuchListener,如果OnTouchListener的onTouch方法返回true那么OnTuchEvent方法就不会被调用,这里也再次证实了OnTouchListener是优先级高于OnTuchEvent

  • OnTouchListener和OnTuchEven对事件是怎么处理的
    OnTouchListener和OnTuchEven他们对事件处理的的方式是相同的,所以在这里我只说OnTuchEven方法

下面是OnTuchEven对事件处理的部分源码


        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.

从上面源码可以看出,只要View的CLICKABLE 和LONG_CLICKABLE有一个为true,那么他就会消耗这个事件,即OnTuchEven返回true,这里需要说明一下LONG_CLICKABLE是默认为false,而CLICKABLE 的返回值是和View的状态有关,如果当前View是可点击的那么CLICKABLE 则为true,如果当前View是不可点击那么CLICKABLE 则为false

这里android的事件分发机制就全部完了。

猜你喜欢

转载自blog.csdn.net/liao5214/article/details/71055206
今日推荐