Android6.0源碼解讀之ViewGroup點擊事件分發機制

    本篇博文是Android點擊事件分發機制系列博文的第三篇,主要是從解讀ViewGroup類的源碼入手,根據源碼理清ViewGroup點擊事件分發原理,明白ViewGroup和View點擊事件分發的關係,並掌握ViewGroup點擊事件分法機制。特別聲明的是,本源碼解讀是基於最新的Android6.0版本。java

各位童鞋能夠參考下面連接進行系統學習
(一)Android6.0觸摸事件分發機制解讀markdown

(二)Android6.0源碼解讀之View點擊事件分發機制app

(三)Android6.0源碼解讀之ViewGroup點擊事件分發機制ide

(四)Android6.0源碼解讀之Activity點擊事件分發機制源碼分析

ViewGroup事件分發中的三個重要方法的源碼解析

    關於ViewGroup事件分發,咱們重點須要解讀dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三個方法。ViewGroup比View多了一個onInterceptTouchEvent攔截事件方法,該方法源碼默認返回false,即ViewGroup默認不攔截任何事件。學習

(一)dispatchTouchEvent源碼解析

/** * 重寫了父類View的dispatchTouchEvent方法 */
    @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;

            // 手指按下去進行一些初始化的處理
            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;// 是否被攔截
            // 若是事件類型ACTION_DOWN或者mFirstTouchTarget不爲空
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {// 當disallowIntercept爲false時
                    intercepted = onInterceptTouchEvent(ev);// 這裏是重點,它會調用onInterceptTouchEvent方法,當該方法爲true時攔截,爲false時不攔截
                    // 所謂的攔截,是指按下去自身以及之後的後續事件move up,攔截下來給本身onTouch使用
                    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) {// 這裏的canceled和intercepted都爲false時,條件成立,也就是說不攔截

                // 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) {// 若是子控件的個數不爲0 且 newTouchTarget爲空
                        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.
                        // 考慮到兩個View交叉重合的狀況,下面的先放進集合,可是按常理說咱們手指先按到上面的,這裏作了一個倒序
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {// 遍歷ViewGroup中的子控件
                            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();// 拿到X、Y位置
                                mLastTouchDownY = ev.getY();
                                // newTouchTarget用到了單向鏈表
                                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) {// 若是newTouchTarge爲空 且 mFirstTouchTarget不爲空
                        // 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.分發給touch targets
            if (mFirstTouchTarget == null) {
                // 沒有觸摸的對象就把它當作一個普通的View
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);// 第三個參數本應爲child,這裏是null意味着須要調用父類View的dispatchTouchEvent方法,而後調用onTouch方法
            } 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;
    }

ViewGroup攔截狀況源碼分析

首先咱們來看一下第34行~48行的代碼,ViewGroup在以下兩種狀況下會判斷是否要攔截當前事件:動畫

事件類型爲ACTION_DOWN或者 mFirstTouchTarget != nullui

    即,當事件由ViewGroup的子元素成功處理時,mFirstTouchTarget 會被賦值並指向子元素,換句話說,當ViewGroup不攔截事件並將事件交由子元素處理時mFirstTouchTarget != null。反過來,一旦事件由當前的ViewGroup攔截時,mFirstTouchTarget != null條件就不成立。那麼當ACTION_MOVE和UP事件到來時,因爲actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null這個條件爲false,致使ViewGroup的onInterceptTouchEvent不會再被調用,而且同一序列中的其餘事件都會默認交給它處理。
    另外,這裏有一種特殊狀況,咱們看36行代碼,有個FLAG_DISALLOW_INTERCEPT標記爲,這個標記是經過requestDisallowInterceptTouchEvent()方法來設置的,通常用在子View中。若是咱們經過reqeustDisallowInterceptTouchEvent()方法設置了FLAG_DISALLOW_INTERCEPT標記位後,ViewGroup將沒法攔截除了ACTION_DOWN之外的其餘方法(即調用該方法並不影響ACTION_DOWN事件處理)。由於ViewGroup會在ACTION_DWON事件到來時作重置狀態操做,這裏從代碼第22~29行能夠看出。this

requestDisallowInterceptTouchEvent源碼解析

/** * {@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);
        }
    }

取消、清理、重置以前的觸摸狀態

/** * Cancels and clears all touch targets. */
    private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {// 若是保存的第一個觸摸View對象不爲空
            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();
            }
        }
    }
    /** * Resets all touch state in preparation for a new cycle. */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

所以咱們能夠得出以下結論:spa

1.當ViewGroup決定攔截事件後,那麼點擊事件將會默認交給它處理而且再也不調用它的onInterceptTouchEvent方法,FLAG_DISALLOW_INTERCEPT這個標記的做用是ViewGroup再也不攔截事件,前提是ViewGroup不攔截ACTION_DOWN事件處理。

2.若是事件可以傳遞到當前的ViewGroup,且咱們要提早處理全部點擊事件,應該選擇dispatchTouchEvent方法,由於只有這個方法能確保每次都會被調用;而onInterceptTouchEvent()卻沒法保證每次事件都會被調用。

3.FLAG_DISALLOW_INTERCEPT標記位能夠用於解決滑動衝突問題。

ViewGroup不攔截狀況源碼分析

    ViewGroup不攔截事件的時候,事件會向下分發交由它的子View進行處理。先來看下代碼64行(!canceled && !intercepted)這裏的canceled和intercepted都爲false時,條件成立,也就是說不攔截。接下來74行的條件判斷:

actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE

    在該if條件內,看到第86行,若是newTouchTarget == null && childrenCount != 0,即子控件的個數不爲0 且 newTouchTarget爲空,在95行中遍歷整個ViewGroup中的子控件,這裏的集合作了個倒序排列,若是兩個View交叉覆蓋在一塊兒,下面的子控件先放進集合,由於後被添加的子控件會浮在上面,一般咱們會但願點擊的時候最上層的那個組件先去響應事件。接着105行代碼開始判斷子控件是否可以接收到點擊事件,主要依賴於兩個條件:第一子控件是否在播動畫;第二點擊事件是否落在子控件的區域內。若是某個子控件知足這兩個條件,那麼事件就會傳遞給它來處理。

buildOrderedChildList方法解析

/** * 實現倒序排序 * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children, * sorted first by Z, then by child drawing order (if applicable). This list must be cleared * after use to avoid leaking child Views. * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated * children. */
    ArrayList<View> buildOrderedChildList() {
        final int count = mChildrenCount;
        if (count <= 1 || !hasChildWithZ()) return null;

        if (mPreSortedChildren == null) {
            mPreSortedChildren = new ArrayList<View>(count);
        } else {
            mPreSortedChildren.ensureCapacity(count);
        }

        final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
        for (int i = 0; i < mChildrenCount; i++) {
            // add next child (in child order) to end of list
            int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
            View nextChild = mChildren[childIndex];
            float currentZ = nextChild.getZ();

            // insert ahead of any Views with greater Z
            int insertIndex = i;
            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                insertIndex--;
            }
            mPreSortedChildren.add(insertIndex, nextChild);
        }
        return mPreSortedChildren;
    }

    接着看代碼,129行經過dispatchTransformedTouchEvent()這一重要方法(後面有詳細分析),判斷是否有子控件,若是有子控件則執行內部的操做,並找到最終觸摸的對象,經過addTouchTarget方法賦值給newTouchTarget。在dispatchTransformedTouchEvent()方法中,若是子控件的dispatchTouchEvent()方法返回true,那麼mFirstTouchTarget就會被賦值,同時跳出for循環,詳見148行代碼。一樣若是dispatchTouchEvent()方法返回false,ViewGroup就會把事件分發給下一個子控件(若是還有下一個子控件)。

    mFirstTouchEvent的真正賦值實際上是在addTouchTarget方法中完成的,mFirstTouchEvent實際上是一個單鏈表結構,若是mFirstTouchEvent爲null,那麼ViewGroup就會默認攔截下來同一序列中全部的點擊事件。

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.next
        mFirstTouchTarget = target;// 將target賦值給mFirstTouchTarget
        return target;
    }

    接着咱們看到171行代碼中,若是mFirstTouchEvent爲null,也就是說要麼ViewGroup中沒有子控件,要麼是子控件處理了點擊事件,可是在dispatchTouchEvent中返回了false(子控件在onTouchEvent中返回了false),那麼ViewGroup就會本身處理點擊事件,須要說明的是175行代碼中,第三個參數本應爲child,這裏是null意味着須要調用父類View的dispatchTouchEvent方法,而後調用onTouch方法。

TouchTarget 內部類源碼解析

/* Describes a touched view and the ids of the pointers that it has captured. * 鏈表實現的內部類,解決多指觸控問題用來指定當前觸摸的對象,多個手指觸控(0~31個手指) * This code assumes that pointer ids are always in the range 0..31 such that * it can use a bitfield to track which pointer ids are present. * As it happens, the lower layers of the input dispatch pipeline also use the * same trick so the assumption should be safe here... */
    private static final class TouchTarget {
        private static final int MAX_RECYCLED = 32;
        private static final Object sRecycleLock = new Object[0];
        private static TouchTarget sRecycleBin;
        private static int sRecycledCount;

        public static final int ALL_POINTER_IDS = -1; // all ones

        // The touched child view.
        public View child;

        // The combined bit mask of pointer ids for all pointers captured by the target.
        public int pointerIdBits;

        // The next target in the target list.
        public TouchTarget next;

        private TouchTarget() {
        }

        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) {
                    target = new TouchTarget();
                } else {
                    target = sRecycleBin;
                    sRecycleBin = target.next;
                     sRecycledCount--;
                    target.next = null;
                }
            }
            target.child = child;
            target.pointerIdBits = pointerIdBits;// 手指的ID
            return target;
        }

        public void recycle() {
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin;
                    sRecycleBin = this;
                    sRecycledCount += 1;
                } else {
                    next = null;
                }
                child = null;
            }
        }
    }

相當重要的dispatchTransformedTouchEvent方法解析

/** * 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) {// 若是child爲空,則調用本身的分發方法
                handled = super.dispatchTouchEvent(event);
            } else {// 不然調用child的分發方法
                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 the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {// 若是ViewGroup中沒有子控件,調用父類View的dispatchTouchEvent
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {// 若是有子控件
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
// 調用子控件的dispatchTouchEvent,有兩種狀況,若是是子控件是View,又分紅兩種狀況(Button返回true,TextView返回false);若是是ViewGroup則進入遞歸了,又回到了這段代碼,最終要麼沒有任何消耗事件的View,要麼找到消費事件的View
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

    該方法在dispatchTouchEvent()中被調用,用於將事件分發給子View處理。咱們重點看一下60~71行代碼。在dispatchTransformedTouchEvent()方法一共有三個參數,其中第三個參數View child有時爲null,有時不爲null。61行代碼中,child==null意味着事件沒有被消費,ViewGroup中沒有子控件須要調用父類View的dispatchTouchEvent方法,即super.dispatchTouchEvent(event)。
    接着咱們關注下handled這個變量,能夠發現dispatchTransformedTouchEvent()方法return handled,而handled的值實際上是取決於dispatchTransformedTouchEvent()方法遞歸調用dispatchTouchEvent()方法的結果,也就是說在子控件中dispatchTouchEvent()方法的onTouchEvent()是否消費了Touch事件的返回值決定了dispatchTransformedTouchEvent()的返回值,從而決定mFirstTouchTarget是否爲null,更進一步決定了ViewGroup是否處理Touch事件。

(二)onInterceptTouchEvent源碼解析

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;// 默認不攔截
    }

    你沒有看錯,這方法就是簡簡單單的一個布爾值返回,當返回true時,對事件進行攔截,返回false則不攔截。

(三)ViewGroup點擊事件分發小結

    Android點擊事件分發是到達頂級View後(通常是ViewGroup),會調用ViewGroup的dispatchTouchEvent方法,其中它的onInterceptTouchEvent方法若是返回true,則會對事件傳遞進行攔截,事件由ViewGroup處理;若是onInterceptTouchEvent方法返回false,則表明不對事件進行攔截,默認返回false,即全部的ViewGroup都是默認不攔截的。則此時子View中的dispatchTouchEvent方法將被調用,到此,事件已經由頂級View傳遞給了下一層的View,接下來的過程是一個遞歸循環的過程,和頂級View事件分發過程是一致的,直到完成整個事件分發。

這裏寫圖片描述

相關文章
相關標籤/搜索