天天看點

ViewGroup對觸摸事件的分發響應過程

以前對于觸摸事件的分發過程有過專門的研究,當時把郭林大神講ViewGroup觸摸分發的部落格(http://blog.csdn.net/guolin_blog/article/details/9153747)看了不下3邊,當時感覺對于分發已經了然于心。但是,後來在項目實戰中發現好多一想不到的問題,郭大神的部落格完全沒法解釋,凸(艹皿艹 ),然後自己打開ide,看ViewGroup源碼發現跟郭大神部落格裡面的源碼完全不一樣,原來郭大神的部落格說得是2.x版本ViewGroup關于觸摸的分發的源碼,無奈,相當于以前的研究都白費了。正好馬上就進行技術分享會,我直接報,我要說觸摸分發的過程。也借此機會,可以好好研究一下觸摸的分發,并且解開我的疑惑。

觸摸分發和響應的過程都是在dispatchTouchEvent方法中完成的,是以,此文主要分析ViewGroup中dispatchTouchEvent方法的實作。

想看懂ViewGroup中dispatchTouchEvent方法的代碼,必須要先大緻了解ViewGroup的一個内部類,TouchTarget類

/* Describes a touched view and the ids of the pointers that it has captured.
     *
     * 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() {
        }

        // 看到這個有沒有很眼熟?是的Message裡也有類似的實作,我們在之前介紹Message的文章裡詳細地分析過
        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) { // 沒有可以回收的目标,則new一個傳回
                    target = new TouchTarget(); 
                } else {
                    target = sRecycleBin; // 重用目前的sRecycleBin
                    sRecycleBin = target.next; // 更新sRecycleBin指向下一個
                     sRecycledCount--; // 重用了一個,可回收的減1
                    target.next = null; // 切斷next指向
                }
            }
            target.child = child; // 找到合适的target後,指派
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() { // 基本是obtain的反向過程
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin; // next指向舊的可回收的頭
                    sRecycleBin = this; // update舊的頭指向this,表示它自己現在是可回收的target(第一個)
                    sRecycledCount += 1; // 多了一個可回收的
                } else {
                    next = null; // 沒有next了
                }
                child = null; // 清空child字段
            }
        }
    }
           

源碼分析:

TouchTarget類用來記錄觸摸對象,包括此次觸摸到的view(child成員變量)和觸摸的id(pointerIdBits成員變量)。TouchTarget自身維護着一個TouchTarget對象形成的連結清單,obtain方法會從這個連結清單頭上取出一個TouchTarget來使用,如果此時連結清單沒有TouchTarget可用,則新造一個給調用端;recycle方法會将不用的TouchTarget對象重置後添加到連結清單的頭部,供以後使用。當多點觸摸的時候,會有多個TouchTarget,這些TouchTarget都是通過obtian方法擷取的,擷取的TouchTarget對象會形成一個觸摸鍊,這個觸摸鍊的起點是mFirstTouchTarget。本文重點說明觸摸的分發響應過程,簡單起見,以單點觸摸的情況來說明,單點觸摸中隻會有一個TouchTarget對象。

下面我們來看ViewGroup中dispatchTouchEvent的實作:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) { // view沒有被遮罩,一般都成立
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(從按下到松手)中的第一個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(); // 作為新一輪的開始,reset所有相關的狀态
            }

            // Check for interception.
            final boolean intercepted; // 檢查是否要攔截
            if (actionMasked == MotionEvent.ACTION_DOWN // down事件
                    || mFirstTouchTarget != null) { // 或者之前的某次事件已經經由此ViewGroup派發給children後被處理掉了
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) { // 隻有允許攔截才執行onInterceptTouchEvent方法
                    intercepted = onInterceptTouchEvent(ev); // 預設傳回false,不攔截
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false; // 不允許攔截的話,直接設為false
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 在這種情況下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
                // 第一次的down事件沒有被此ViewGroup的children處理掉(要麼是它們自己不處理,要麼是ViewGroup從一
                // 開始的down事件就開始攔截),則接下來的所有事件
                // 也沒它們的份,即不處理down事件的話,那表示你對後面接下來的事件也不感興趣
                intercepted = true; // 這種情況下設定ViewGroup攔截接下來的事件
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了

            // Update list of touch targets for pointer down, if needed.
            // 是否拆分事件,3.0(包括)之後引入的,預設拆分
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null; // 接下來ViewGroup判斷要将此touch事件交給誰處理
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) { // 沒取消也不攔截,即是個有效的touch事件
                if (actionMasked == MotionEvent.ACTION_DOWN // 第一個手指down
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下來的手指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 View[] children = mChildren;

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        // 從最後一個向第一個找
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue; // 不滿足這2個條件直接跳過,看下一個child
                            }
                            
                            // child view能receive touch事件而且touch坐标也在view邊界内

                            newTouchTarget = getTouchTarget(child);// 查找child對應的TouchTarget
                            if (newTouchTarget != null) { // 比如在同一個child上按下了多跟手指
                                // 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; // newTouchTarget已經有了,跳出for循環
                            }

                            resetCancelNextUpFlag(child);
                            // 将此事件交給child處理
                            // 有這種情況,一個手指按在了child1上,另一個手指按在了child2上,以此類推
                            // 這樣TouchTarget的鍊就形成了
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 如果處理掉了的話,将此child添加到touch鍊的頭部
                                // 注意這個方法内部會更新 mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已經被處理了
                                break; // 可以退出for循環了。。。
                            }
                        }
                    }

                    // 本次沒找到newTouchTarget但之前的mFirstTouchTarget已經有了
                    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;
                        }
                        // while結束後,newTouchTarget指向了最初的TouchTarget
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            // 非down事件直接從這裡開始處理,不會走上面的一大堆尋找TouchTarget的邏輯
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 沒有children處理則派發給自己處理
                // 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) { // 周遊TouchTarget形成的連結清單
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true; // 已經處理過的不再讓其處理事件
                    } else {
                        // 取消child标記
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 如果ViewGroup從半路攔截了touch事件則給touch鍊上的child發送cancel事件
                        // 如果cancelChild為true的話
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true; // TouchTarget鍊中任意一個處理了則設定handled為true
                        }
                        if (cancelChild) { // 如果是cancelChild的話,則回收此target節點
                            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) {
                // 取消或up事件時resetTouchState
                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; // 傳回處理的結果
    }
           

源碼分析(以代碼執行的順序分析):

重要成員變量

// First touch target in the linked list of touch targets.
    private TouchTarget mFirstTouchTarget;//ViewGroup的child消耗touch,此變量記錄下消耗touch的view

    protected int mGroupFlags;//各種flag值的集合,其中包含FLAG_DISALLOW_INTERCEPT
           

1.觸摸以MotionEvent.ACTION_DOWN為開始,在觸摸的開始,ViewGroup會清除所有的關于上次觸摸的資訊。

8行一般為true,13行判斷如果是ACTION_DOWN,則調用cancelAndClearTouchTargets()和resetTouchState(),在這兩個方法中分别mFirstTouchTarget=null和重置mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位。

代碼如下:

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

    /**
     * Resets the cancel next up flag.
     * Returns true if the flag was previously set.
     */
    private static boolean resetCancelNextUpFlag(View view) {
        if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

    /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }
           

5行如果mFirsetTouchTarget!=null,會走到19行調用clearTouchTargets(),clearTouchTargets()中将mFirstTouchTarget為起點的連結清單都清空,最終将mFirstTouchTarget=null.

/**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
           

FLAG_DISALLOW_INTERCEPT标志位,決定了ViewGroup是否允許攔截touch事件,往往通過child.getParent().requestDisallowInterceptTouchEvent(true)方法來設定,當傳入true的話,child的所有父控件都不會攔截touch事件,這樣保證了child肯定會收到此touch事件。

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

tip:我們自己做項目時經常會重寫child的onTouchEvent方法,判斷當情況A時調用child.getParent().requestDisallowInterceptTouchEvent(true),來讓父控件不去攔截touch事件,當情況B時,調用child.getParent().requestDisallowInterceptTouchEvent(false),運許父控件攔截touch事件,如果此時父控件也需要攔截touch,那麼父控件會攔截touch,child再無法收到touch事件,child的onTouchEvent方法也就再得不到執行。但是,當使用者松開手機再按下,産生下一個觸摸事件時,child的onTouchEvent又得到執行,就是因為父控件ViewGroup在dispatchTouchEvent方法中判斷MotionEvent的action為ACTION_DOWN,重置了mGroupFlags标志位所緻。

2.ViewGroup通過MotionEvent的action,mFirstTouchTarget,mGroupFlags中FLAG_DISALLOW_INTERCEPT,三個值共同判斷是否要攔截此touch。

(不攔截touch,ViewGroup會根據觸摸在哪個view上,将touch逐層分發到那個view去,即逐層調用view的dispatchTouchEvent方法,如果攔截touch,則不會向下分發,直接調用ViewGroup的onTouchEvent方法,之後便結束了此ViewGroup的dispatchTouchEvent方法)

dispatchTouchEvent代碼片段:

22到40行代碼判斷ViewGroup是否攔截touch事件

mFirstTouchTarget記錄的是ViewGroup中消耗了touch事件的child的引用;

當一個新的觸摸事件到來,由于之前重置了ViewGroup中記錄的上一個touch的資訊,此時MotionEvent的action為ACTION_DOWN并且mFirstTouchTarget==null,mGroupFlags中FLAG_DISALLOW_INTERCEPT為false,是以,進入27行代碼,即此處觸摸是否攔截又ViewGroup的onInterceptTouch方法決定。

當MotionEvent為非ACTION_DOWN時,如果mFirstTouchEvent!=null,即ViewGroup中被觸摸到的子View消耗了此touch事件,此時是否攔截touch由mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位值和onInterceptTouch方法傳回值共同決定。

當MotionEvent為非ACTION_DOWN時,如果mFirstTouchEvent==null,即ViewGroup中被觸摸到的子View沒有消耗此touch事件,進入39行,ViewGroup攔截touch事件。

tips:當我們自定義的View重寫onTouchEvent方法,在onTouchEvent方法的ACITON_DOWN中傳回false,此View便再無法收到其後的touch事件,就是因為ViewGroup的子View沒有消耗touch事件,mFirstTouchTarget==null,intercept=true,ViewGroup便會攔截其後的touch事件。

3.根據intercept的值,決定是否攔截touch事件,不攔截将touch傳遞到觸摸到的View上去,當分發到的View消耗了此touch,mFirstTouchTarget會記下此View;分發到的View沒有消耗此touch,mFirstTouchTarget一直為null。

51行if(!cancel&&!intercepted),預設cancel為false,當intercept為false,即不攔截時,代碼會走到64行,childrenCount !=0,即ViewGroup有child,會走到73行,從後往前周遊ViewGroup的所有child,77,78行判斷是否觸摸在此child上,并且此child接受觸摸事件。

tips:我們經常使用FrameLayout來做重疊的布局,當我們的觸摸事件落在多個重疊view上時,這時候如果前邊的view消耗了事件,後邊的view就收不到觸摸事件了;就是因為ViewGroup在分發touch的時候,是從後往前周遊的(view重疊時,就是從上向下),如果上面的view消耗了touch,後邊的view自然就接收不到touch了。

84-92行主要是對多點觸控進行處理,暫時不管,代碼進入關鍵的96行,調用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),此方法将dispatchTouchEvent方法中傳入的MotionEvent的相對于ViewGroup的x,y坐标轉化為相對于child的x,y坐标,然後,分發給此child。

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) {
                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 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) {
            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());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

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

13行,如果傳入的cancel==true或者event.getAction()==MotionEvent.ACTION_CANCEL,把event的action設定為ACTION_CANCEL。

15行,如果child==null,super.dispatchTouchEvent();否則,child.dispatchTouchEvent();

super.dispatchTouchEvent()就是調用view的dispatchTouchEvent()方法,view的dispatchTouchEvent實作,其實就是調用自身的onTouchEvent(),即child==null,調用ViewGroup的onTouchEvent(),child!=null,将touch分發給傳入的child。

20行,恢複event的action為原來的action;

38-57行,60-71行也是類似邏輯,如果child==null,super.dispatchTouchEvent();否則,child.dispatchTouchEvent();

由dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)調用參數可知,最終執行了child.dispatchTouchEvent()将觸摸事件分發給了子view。

下面為View的dispatchTouchEvent源碼

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) { // 一般都成立
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED狀态下嘗試調用onTouch方法
                return true; // 如果被onTouch處理了,則直接傳回true
            }
            // 從這裡我們可以看出,當你既設定了OnTouchListener又設定了OnClickListener,那麼目前者傳回true的時候,
            // onTouchEvent沒機會被調用,當然你的OnClickListener也就不會被觸發;另外還有個差別就是onTouch裡可以
            // 收到每次touch事件,而onClickListener隻是在up事件到來時觸發。
            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false; // 上面的都沒處理,則傳回false
    }
           

View的dispatchTouchEvent方法很簡單,16-17行,就是判斷有沒有設定OnTouchListener,如果設定了,并且OnTouchListener.onTouch()傳回了true,則dispatchTouchEvent直接傳回true;如果沒有設定OnTouchListener或者OnTouchListener.onTouch()傳回false,則到23行,調用onTouchEvent(),将onTouchEvent()的傳回值,作為dispatchTouchEvent的傳回值。

繼續說ViewGroup的dispatchTouchEvent過程,如果child消耗了此touch,則會進入96行的if語句塊中,最終執行到第104行,調用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;
    }
           

看以上代碼,我們知道此方法就是在拿消耗掉觸摸的child生成一個TouchTarget,添加到touch鍊的頭部,并且mFirstTouchTarget指向此TouchTarget。主要就是更新mFirstTouchTarget的引用指向,消耗了此touch的child。

繼續分析ViewGroup的dispatchTouchEvent方法

前面所有的判斷機會都要求action為ACTION_DOWN,126行後終于沒有了這條判斷,也就是處理所有action。

126行,如果此ViewGroup的child沒有處理此touch,mFirstTouchTarget==null,調用dispatchTransformedTouchEvent(ev, canceled, null, idBitsToAssign); 由上面分析dispatchTransformedTouchEvent方法代碼可知,第三個參數child為null,會調用super.dispatchTouchEvent(),即ViewGroup的onTouchEvent。

也就是如果ViewGroup的child不消耗此touch,則調用ViewGroup的onTouchEvent方法。

134行後代碼,則代表ViewGroup中有child消耗了此touch,135行将mFirstTouchTarget指派給target,因為進入此else語句mFirstTouchTarget肯定不為空,是以target不為空,進入136行while循環,138行的if判斷,對應于上邊96行的if括号内的代碼,如果這個touch第一次進行分發,走入了96行的if括号,也就是此touch的ACTION_DOWN被子view消耗後,代碼走到138行,直接handled=true,不再處理。如果不是第一次分發,49行newTouchTarget=null,MotionEvent的action不是ACTION_DOWN,不會進入51,52行if語句塊;是以這裡會走到else語句。142行就是判斷是否取消的操作,或者本來沒有攔截現在變為了攔截。這裡我們不讨論取消的情況,隻考慮攔截和不攔截的情況,即cancelChild的值去intercepted的值。如果不攔截,cancelChild==false,調用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),會将此touch直接分發給mFirstTouchTarget記錄的child;如果攔截(ViewGroup由原來不攔截件變為攔截),cancelChild==true,調用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),會将ACTION_CANCEL分發給mFirstTouchTarget記錄的child。

150行,如果cancelChild==true,predecessor會一直為null,因為158行有continue語句,161行predecessor=target這句永遠得不到執行,最終會把mFirstTouchTarget置為null,這樣會導緻23行的if語句塊再得不到執行,及onInterceptTouchEvent方法再得不到執行。如果cancelChild==false,則mFirstTouchTarget會儲存不變,隻是把此touch分發到mFirstTouchTarget記錄的child上去。

tips:如果ViewGroup的子View消耗了touch的ACTION_DOWN,此touch的後續事件,不再重新分發,直接傳遞到消耗ACTION_DOWN的View上去。

 如果ViewGroup一開始不攔截,在後續事件中變為攔截(在onInterceptTouchEvent方法中根據滑動動作,傳回了true),原來消耗touch的子View會受到ACTION_CANCEL事件,并且ViewGroup的onInterceptTouchEvent方法在此次touch事件中再不會執行。

nnd,終于寫完了,寫了好幾天,語言不太會組織,程式員通病吧,單點觸控隻有一個TouchTarget,多點觸控按下幾個手指這幾個手指觸點會形成一個TouchTarget的鍊,簡單起見,多點觸控就沒講,單點和多點事件分發處理的過程是完全一樣的的。大夥看思路就好。如有了解不一緻的地方,還望在評論區留言讨論。