Android點擊事件簡單梳理

View與ViewGroup TouchEvent分析
ViewGroup繼承于View,而View實現(xiàn)了兩個方法 :
dispatchTouchEvent 主要用于分發(fā)事件较坛, 在不考慮父view group攔截的情況下隙券,所有touch事件首先產(chǎn)生down事件嗅绸,父view從上層獲取MotionEvent再調(diào)用子類的dispatchTouchEvent將事件傳遞給子View麻裁。
在子view的dispatchTouchEvent方法中渐夸,優(yōu)先判定是否存在touchListerner , 如果有則調(diào)用touchlisterner的touch方法嗤锉。當(dāng)返回true時,dispatchTouchEvent將返回true墓塌,并且不再執(zhí)行自己的onTouchEvent方法瘟忱。否則dispatchTouchEvent將返回子viewonTouchEvent的返回值 。

當(dāng)子view在dispatchTouchEvent的down事件里返回true之后苫幢,父view會將當(dāng)前子view設(shè)置成targetView访诱。只有target view才會收到后續(xù)的move ,up ,或者cancel事件韩肝。

說完子view触菜,再看父類的viewGroup, 父view也是由上層調(diào)用自己的dispatchTouchEvent來分發(fā)MotionEvent,事件的傳遞邏輯是先調(diào)用dispatchTouchEvent方法哀峻,然后調(diào)用onInterceptTouchEvent判斷是否攔截涡相,這里默認(rèn)是不攔截,攔截的情況后續(xù)再說剩蟀。再之后

相比view 催蝗,view group多了一個onInterceptTouchEvent方法。
該方法主要用于決定是否攔截子view獲取MotionEvent育特。默認(rèn)實現(xiàn)為false,也就是不攔截丙号。當(dāng)返回true之后,則直接將當(dāng)子view的后續(xù)點擊事件傳遞給父view.

整個流程里最復(fù)雜的部分為viewgroup的dispatchTouchEvent方法。

當(dāng)點擊事件為ACTION_DOWN的時候犬缨,首先清除當(dāng)前view group 處理點擊事件的TouchTarget鏈表喳魏,恢復(fù)view group到默認(rèn)狀態(tài)(在detach的時候也會做)。
接著判斷是否需要攔截遍尺,如果攔截則直接調(diào)用dispatchTransformedTouchEvent方法并傳入空view(當(dāng)孩子為空時默認(rèn)調(diào)用自己的super.dispatchTouchEvent方法)截酷。
當(dāng)不攔截點擊事件為ACTION_POINTER_DOWN ||ACTION_POINTER_DOWN的時候涮拗,根據(jù)從子view找出符合點擊區(qū)域的孩子依次調(diào)用子view的dispatchTouchEvent方法來判斷孩子是否處理乾戏,不論是否找到都會調(diào)用dispatchTransformedTouchEvent方法。當(dāng)有子view處理的時候會生成一個TouchTarget并加入到鏈表中三热。

對于其他的事件鼓择,當(dāng)TouchTarget鏈表為空時直接調(diào)用dispatchTransformedTouchEvent方法并傳入空view丟給自己處理,否則遍歷TouchTarget鏈表找到合適的子view處理就漾。
在cancel或者up事件的時候會清理鏈表呐能。

  @Override  
    public boolean dispatchTouchEvent(MotionEvent ev) {  
        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);  
        }  
  
        boolean handled = false;  
        if (onFilterTouchEventForSecurity(ev)) {  
            final int action = ev.getAction();  
            final int actionMasked = action & MotionEvent.ACTION_MASK;  
  
            /** 
             * 第一步:對于ACTION_DOWN進(jìn)行處理(因為ACTION_DOWN是一系列事件的開端) 
             *  
             * 1. 清除以往的Touch狀態(tài)(state)開始新的手勢(gesture) 
             * cancelAndClearTouchTargets(ev)中有一個非常重要的操作: 
             * 將mFirstTouchTarget設(shè)置為null!!!! 
             * 2. 隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識 
             */  
            if (actionMasked == MotionEvent.ACTION_DOWN) {  
                // Throw away all previous state when starting a new touch gesture.  
                cancelAndClearTouchTargets(ev);//取消和清除所有的touch target  
                resetTouchState();//重置所有touch狀況,準(zhǔn)備新的touch狀況周期  
            }  
  
              
       /** 
        * 第二步:檢查是否要攔截(Check for interception) 
        * 在哪些情況下會調(diào)用該代碼呢抑堡?有如下幾種情況 
        * 1 處理ACTION_DOWN事件時 
        * 2 當(dāng)ACTION_DOWN事件被子View消費后處理ACTION_MOVE和ACTION_UP時 
        *     因為此時mFirstTouchTarget!=null摆出。所以此時ViewGroup 
        *     是有機(jī)會攔截ACTION_MOVE和ACTION_UP的,但是我們也可以調(diào)用方法: 
        *     requestDisallowInterceptTouchEvent來禁止ViewGroup的事件攔截. 
        *     如果子View沒有消費Touch事件,那么那么當(dāng)后續(xù)的ACTION_MOVE和ACTION_UP 
        *     到來時是不會調(diào)用到本處代碼的. 
        *   
        * ViewGroup.dispatchTouchEven中,使用變量intercepted來標(biāo)記ViewGroup是否攔截Touch事件的傳遞. 
        * 該變量在后續(xù)代碼中起著很重要的作用. 
        *  
        * 從此處if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)及其內(nèi)部代碼可知: 
        * 當(dāng)ViewGroup決定攔截事件后,那么后續(xù)的點擊事件將會默認(rèn)交給它處理,不再調(diào)用onInterceptTouchEvent() 
        *    因為在處理ACTION_DOWN時如果Touch事件被子View消費,那么mFirstTouchTarget不為空; 
        *    反之,如果Touch事件沒有被子View消費,那么mFirstTouchTarget為空,即此時Touch由當(dāng)前 
        *    的ViewGroup攔截首妖。此時當(dāng)ACTION_MOVE和ACTION_UP來到時,不再滿足: 
        *    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) 
        *    當(dāng)然也就無法調(diào)用其內(nèi)部的onInterceptTouchEvent()偎漫。 
        * 通俗地說:一旦ViewGroup攔截了ACTION_DOWN事件由自身的onTouchEvent()處理,那么 
        * 對于后續(xù)的ACTION_MOVE和ACTION_UP而言ViewGroup不再調(diào)用onInterceptTouchEvent() 
        *  
        * 這里有個東西需要注意:FLAG_DISALLOW_INTERCEPT 
        * 在子View中調(diào)用requestDisallowInterceptTouchEvent()后造成disallowIntercept為true 
        * 即禁止攔截.于是不滿足if(!disallowIntercept)所以也就調(diào)用不到該if內(nèi)的onInterceptTouchEvent() 
        * 自然就沒有辦法攔截了. 
        * 但是requestDisallowInterceptTouchEvent()對于ACTION_DOWN是無效的. 
        * 因為對于ACTION_DOWN會調(diào)用 cancelAndClearTouchTargets(ev)和resetTouchState(); 
        * 對FLAG_DISALLOW_INTERCEPT等狀態(tài)值復(fù)原重置(參考上面的代碼) 
        *  
        * 舉兩種情況說明: 
        * 1 當(dāng)處理ACTION_DOWN時當(dāng)然會滿足 
        *  if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) 
        *  對于ACTION_DOWN子View有兩種處理結(jié)果 
        *  1.1 消耗了Touch事件,那么mFirstTouchTarget不為null. 
        *      所以處理后續(xù)的ACTION_MOVE和ACTION_UP時依然滿足該if判斷 
        *  1.2 沒有消耗Touch事件.mFirstTouchTarget=null.不滿足該if. 
        *      所以后續(xù)的ACTION_MOVE和ACTION_UP由ViewGroup處理,此時再討論什么攔截也就沒有意義了. 
        *      同樣的道理當(dāng)子View消費了ACTION_DOWN后當(dāng)處理ACTION_MOVE的時候ViewGroup攔截了該事件 
        *      那么當(dāng)ACTION_UP隨之到來時由于mFirstTouchTarget=null所以不會再調(diào)用該段代碼,自然也就 
        *      不會調(diào)用onInterceptTouchEvent()判斷是否攔截了.這點在上面的注釋也有提及 
        * 2 當(dāng)出現(xiàn)1.1的情況時滿足該if判斷. 
        *  如果在子View中調(diào)用了requestDisallowInterceptTouchEvent()那么就禁止攔截 
        *  即disallowIntercept=true.所以不滿足if (!disallowIntercept)當(dāng)然也就調(diào)用不到 
        *  onInterceptTouchEvent(ev)了,而是執(zhí)行else{ intercepted = false;} 
        *  也就是說ViewGroup無法攔截Touch了. 
        */  
            final boolean intercepted;//使用變量intercepted來標(biāo)記ViewGroup是否攔截Touch事件的傳遞.  
  
            // 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經(jīng)找到能夠接收touch事件的目標(biāo)組件)時if成立  
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {  
                //判斷disallowIntercept(禁止攔截)標(biāo)志位  
        //因為在其他地方可能調(diào)用了requestDisallowInterceptTouchEvent()改變該值.  
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
                //當(dāng)禁止攔截為false時(即disallowIntercept為false)調(diào)用onInterceptTouchEvent(ev)方法  
                if (!disallowIntercept) {  
                    //既然disallowIntercept為false那么就調(diào)用onInterceptTouchEvent()方法將結(jié)果賦值給intercepted  
                    //這就是常說的事件傳遞流程:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent  
                    //注意:這是dispatchTouchEvent中唯一調(diào)用onInterceptTouchEvent()的地方  
                    //所以,只有2種情況系統(tǒng)會調(diào)用onInterceptTouchEvent:  
                    //1.派發(fā)action_donwn有缆;2.ViewGroup里有子控件攔截或消費了action_down  
                    //換句話說象踊,若ViewGroup攔截消費了down,那么它在接收后續(xù)的up和move時不再調(diào)用onInterceptTouchEvent  
                    intercepted = onInterceptTouchEvent(ev);  
                    ev.setAction(action); // restore action in case it was changed  
                } else {  
                    //禁止攔截的FLAG為ture說明沒有必要去執(zhí)行是否需要攔截了能夠順利通過,所以設(shè)置攔截變量為false  
                    //即棚壁,當(dāng)禁止攔截為true時(即disallowIntercept為true)設(shè)置intercepted = false  
                    intercepted = false;  
                }  
            } else {  
                //當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(即沒有Touch的目標(biāo)組件)時  
                //設(shè)置 intercepted = true表示ViewGroup執(zhí)行Touch事件攔截的操作杯矩。  
                //There are no touch targets and this action is not an initial down  
                //so this view group continues to intercept touches.  
                intercepted = true;  
            }  
  
              
            /** 
             * 第三步:檢查cancel(Check for cancelation) 
             */  
            final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;  
  
              
            /** 
             * 第四步:事件分發(fā)(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;  
            //不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為false(不攔截)  
            if (!canceled && !intercepted) { //這是intercepted體現(xiàn)作用的唯一一行代碼  
                //處理ACTION_DOWN事件.這個環(huán)節(jié)比較繁瑣.  
                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 (childrenCount != 0) {  
                        // 依據(jù)Touch坐標(biāo)尋找子View來接收Touch事件  
                        // Find a child that can receive the event.  
                        // Scan children from front to back.  
                        final View[] children = mChildren;  
                        final float x = ev.getX(actionIndex);  
                        final float y = ev.getY(actionIndex);  
  
                        final boolean customOrder = isChildrenDrawingOrderEnabled();  
                        // 遍歷子View判斷哪個子View接受Touch事件  
                        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;  
                            }  
  
                            newTouchTarget = getTouchTarget(child);  
                            if (newTouchTarget != null) {  
                                // 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.  
                                // 既然已經(jīng)找到了,所以執(zhí)行break跳出for循環(huán)  
                                // 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不滿足,當(dāng)然也不會執(zhí)行break語句. 
                             * 于是代碼會執(zhí)行到這里來. 
                             *  
                             *  
                             * 調(diào)用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做 
                             * 遞歸處理(也就是遍歷該子View的View樹) 
                             * 該方法很重要,看一下源碼中關(guān)于該方法的描述: 
                             * 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. 
                             * 將Touch事件傳遞給特定的子View. 
                             * 該方法十分重要!!!!!!!!!!!!!!!!! 
                             * 在該方法中為一個遞歸調(diào)用,會遞歸調(diào)用dispatchTouchEvent()方法!!!!!!!!!! 
                             * 在dispatchTouchEvent()中: 
                             * 如果子View為ViewGroup并且Touch沒有被攔截那么遞歸調(diào)用dispatchTouchEvent() 
                             * 如果子View為View那么就會調(diào)用其onTouchEvent(),這個不再贅述. 
                             *  
                             *  
                             * 該方法返回true則表示子View消費掉該事件,同時進(jìn)入該if判斷. 
                             * 滿足if語句后重要的操作有: 
                             * 1 給newTouchTarget賦值 
                             * 2 給alreadyDispatchedToNewTouchTarget賦值為true. 
                             *   看這個比較長的英語名字也可知其含義:已經(jīng)將Touch派發(fā)給新的TouchTarget 
                             * 3 執(zhí)行break. 
                             *   因為該for循環(huán)遍歷子View判斷哪個子View接受Touch事件,既然已經(jīng)找到了 
                             *   那么就跳出該for循環(huán). 
                             * 4 注意: 
                             *   如果dispatchTransformedTouchEvent()返回false即子View的onTouchEvent返回false 
                             *   (即Touch事件未被消費)那么就不滿足該if條件.所以也就無法執(zhí)行addTouchTarget(). 
                             *   在此簡單說一下addTouchTarget()中涉及到的ViewGroup的一個內(nèi)部類TouchTarget——它是一個事件鏈. 
                             *   該處的mFirstTouchTarget就是一個TouchTarget.它保存了可以消耗Touch事件的View. 
                             *   在該處,如果dispatchTransformedTouchEvent()返回true即子View的onTouchEvent返回true則說明 
                             *   該View消耗了Touch事件,那么將該View加入到事件鏈中!!!!!!!!!!!!!!! 
                             *   尤其注意: 
                             *   這個操作是在處理ACTION_DOWN的代碼塊里進(jìn)行的.即是在: 
                             *    if (actionMasked == MotionEvent.ACTION_DOWN||  
                             *    (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) ||  
                             *    actionMasked == MotionEvent.ACTION_HOVER_MOVE) 
                             *    這個大的if判斷中處理的. 
                             *    當(dāng)處理ACTION_MOVE事件和ACTION_UP事件的時候是不會進(jìn)入這個if判斷的!!!!! 
                             *    而是直接從去判斷mFirstTouchTarget!!!!!!!!!!!!!!!! 
                             *    所以如果一個View不處理ACTION_DOWN那么該,那么該View是不會保存在mFirstTouchTarget 
                             *    中的,也就無法繼續(xù)處理ACTION_MOVE事件和ACTION_UP事件!!!!!!!!!!即若該View不消耗 
                             *    ACTION_DOWN事件那么系統(tǒng)是不會講ACTION_MOVE和ACTION_UP事件傳給給該View的 
                             * 5 注意: 
                             *   如果dispatchTransformedTouchEvent()返回true即子View 
                             *   的onTouchEvent返回true(即Touch事件被消費)那么就滿足該if條件. 
                             *   從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!! 
                             * 6 小結(jié): 
                             *   對于此處ACTION_DOWN的處理具體體現(xiàn)在dispatchTransformedTouchEvent() 
                             *   該方法返回boolean,如下: 
                             *   true---->事件被消費----->mFirstTouchTarget!=null 
                             *   false--->事件未被消費--->mFirstTouchTarget==null 
                             *   因為在dispatchTransformedTouchEvent()會調(diào)用遞歸調(diào)用dispatchTouchEvent()和onTouchEvent() 
                             *   所以dispatchTransformedTouchEvent()的返回值實際上是由onTouchEvent()決定的. 
                             *    
                             *   簡單地說onTouchEvent()是否消費了Touch事件(true or false)的返回值決定了 
                             *   dispatchTransformedTouchEvent()的返回值!!!!從而決定了mFirstTouchTarget是否為null!!!!!! 
                             *   從而進(jìn)一步?jīng)Q定了ViewGroup是否處理Touch事件.這一點在下面的代碼中很有體現(xiàn). 
                             */  
                            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();  
                                //調(diào)用addTouchTarget()將child添加到mFirstTouchTarget鏈表的表頭  
                                //注意在addTouchTarget()方法內(nèi)部會對mFirstTouchTarget操作,使其不為null  
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);  
                                alreadyDispatchedToNewTouchTarget = true;  
                                break;  
                            }  
                        }//for循環(huán)結(jié)束  
                    }  
  
                      
                    /** 
                     * 該if條件表示: 
                     * 經(jīng)過前面的for循環(huán)沒有找到子View接收Touch事件并且之前的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;  
                        }  
                        //newTouchTarget指向了最初的TouchTarget  
                        newTouchTarget.pointerIdBits |= idBitsToAssign;  
                    }//處理ACTION_DOWN結(jié)束  
                }  
            }  
  
              
              
            /** 
             * 經(jīng)過上面對于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況: 
             * (當(dāng)然如果不是ACTION_DOWN就不會經(jīng)過上面較繁瑣的流程而是從此處開始執(zhí)行,比如ACTION_MOVE和ACTION_UP) 
             *  
             * 情況1 mFirstTouchTarget為null 
             *       即沒有找到能夠消費touch事件的子組件或者是touch事件被攔截了 
             * 情況2 mFirstTouchTarget不為null 
             *       即找到了能夠消費touch事件的子組件則后續(xù)的touch事件都可以傳遞到子View 
             * 這兩種情況的詳細(xì)分析見下. 
             *  
             * 這兩種情況下都會去調(diào)用方法: 
             * dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits) 
             * 我們重點關(guān)注該方法的第三個參數(shù)View child. 
             * 詳情請參加下面dispatchTransformedTouchEvent()源碼分析 
             * 在該源碼中解釋了: 
             * 為什么子view對于Touch事件處理返回true那么其上層的ViewGroup就無法處理Touch事件了!!!!!!!!! 
             * 為什么子view對于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件!!!!!!!!!! 
             *  
             */  
            if (mFirstTouchTarget == null) {  
                /** 
                 * 情況1:mFirstTouchTarget為null 
                 *  
                 * 經(jīng)過上面的分析mFirstTouchTarget為null就是說Touch事件未被消費. 
                 * 即沒有找到能夠消費touch事件的子組件或Touch事件被攔截了, 
                 * 則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣. 
                 * 即子View沒有消費Touch事件,那么子View的上層ViewGroup才會調(diào)用其onTouchEvent()處理Touch事件. 
                 * 在源碼中的注釋為:No touch targets so treat this as an ordinary view. 
                 * 也就是說此時ViewGroup像一個普通的View那樣調(diào)用dispatchTouchEvent(),且在dispatchTouchEvent() 
                 * 中會去調(diào)用onTouchEvent()方法. 
                 * 具體的說就是在調(diào)用dispatchTransformedTouchEvent()時第三個參數(shù)為null. 
                 * 第三個參數(shù)View child為null會做什么樣的處理呢? 
                 * 請參見下面dispatchTransformedTouchEvent()的源碼分析 
                 */  
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);  
            } else {  
                /** 
                 * 情況2:mFirstTouchTarget不為null 
                 * 即找到了可以消費Touch事件的子View且后續(xù)Touch事件可以傳遞到該子View 
                 * 在源碼中的注釋為: 
                 * 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) {  
                        //如果前面利用ACTION_DOWN事件尋找符合接收條件的子組件的同時消費掉了ACTION_DOWN事件  
                        //那么這里為handled賦值為true  
                        handled = true;  
                    } else {  
                        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;  
                        //對于非ACTION_DOWN事件繼續(xù)傳遞給目標(biāo)子組件進(jìn)行處理  
                        //依然是遞歸調(diào)用dispatchTransformedTouchEvent()  
                        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;  
                }  
            }  
  
            /** 
             * 處理ACTION_UP和ACTION_CANCEL 
             * Update list of touch targets for pointer up or cancel, if needed. 
             * 在此主要的操作是還原狀態(tài) 
             */  
            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;  
    }  
      
      
      
    //=====================以上為dispatchTouchEvent()源碼分析======================  
      
      
      
      
      
      
      
    //===============以下為dispatchTransformedTouchEvent()源碼分析=================  
      
    /** 
     * 在dispatchTouchEvent()中會調(diào)用dispatchTransformedTouchEvent()將事件分發(fā)給子View處理 
     *  
     * 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. 
     *  
     * 在此請著重注意第三個參數(shù):View child 
     * 在dispatchTouchEvent()中多次調(diào)用dispatchTransformedTouchEvent(),但是有時候第三個參數(shù)為null,有時又不是. 
     * 那么這個參數(shù)是否為null有什么區(qū)別呢袖外? 
     * 在如下dispatchTransformedTouchEvent()源碼中可見多次對于child是否為null的判斷,并且均做出如下類似的操作: 
     * if (child == null) { 
     *       handled = super.dispatchTouchEvent(event); 
     *    } else { 
     *       handled = child.dispatchTouchEvent(event); 
     * } 
     * 這個代碼是什么意思呢? 
     *  
     * 當(dāng)child == null時會將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理. 
     * 即super.dispatchTouchEvent(event) 
     * 正如源碼中的注釋描述的一樣: 
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. 
     *  
     * 當(dāng)child != null時會調(diào)用該子view(當(dāng)然該view可能是一個View也可能是一個ViewGroup)的dispatchTouchEvent(event)處理. 
     * 即child.dispatchTouchEvent(event); 
     *  
     * 那么該child是否為null又是由什么決定的呢史隆? 
     * 在dispatchTouchEvent()中已經(jīng)知道了: 
     * 如果Touch事件被消耗掉那么child不為null 
     * 如果Touch事件未被消耗掉那么child為null 
     *  
     * 這就解釋了: 
     * 為什么子view對于Touch事件處理返回true那么其上層的ViewGroup就無法處理Touch事件了!!!!!!!!! 
     * 為什么子view對于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件!!!!!!!!!! 
     */  
    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) {  
                    //調(diào)用自身的dispatchTouchEvent()!!!!!!  
                    handled = super.dispatchTouchEvent(event);  
                } else {  
                    final float offsetX = mScrollX - child.mLeft;  
                    final float offsetY = mScrollY - child.mTop;  
                    event.offsetLocation(offsetX, offsetY);  
                    //調(diào)用子View的dispatchTouchEvent()!!!!!!  
                    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);  
        }  
  
        transformedEvent.recycle();  
        return handled;  
    }  
  //===============以上為dispatchTransformedTouchEvent()源碼分析=================  
  
}  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市曼验,隨后出現(xiàn)的幾起案子逆害,更是在濱河造成了極大的恐慌,老刑警劉巖蚣驼,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魄幕,死亡現(xiàn)場離奇詭異,居然都是意外死亡颖杏,警方通過查閱死者的電腦和手機(jī)纯陨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翼抠,你說我怎么就攤上這事咙轩。” “怎么了阴颖?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵活喊,是天一觀的道長。 經(jīng)常有香客問我量愧,道長钾菊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任偎肃,我火速辦了婚禮煞烫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘累颂。我一直安慰自己滞详,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布紊馏。 她就那樣靜靜地躺著料饥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朱监。 梳的紋絲不亂的頭發(fā)上岸啡,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音赌朋,去河邊找鬼凰狞。 笑死,一個胖子當(dāng)著我的面吹牛沛慢,可吹牛的內(nèi)容都是我干的赡若。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼团甲,長吁一口氣:“原來是場噩夢啊……” “哼逾冬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躺苦,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤身腻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匹厘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘀趟,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年愈诚,在試婚紗的時候發(fā)現(xiàn)自己被綠了她按。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牛隅。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酌泰,靈堂內(nèi)的尸體忽然破棺而出媒佣,到底是詐尸還是另有隱情,我是刑警寧澤陵刹,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布默伍,位于F島的核電站,受9級特大地震影響衰琐,放射性物質(zhì)發(fā)生泄漏也糊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一碘耳、第九天 我趴在偏房一處隱蔽的房頂上張望显设。 院中可真熱鬧框弛,春花似錦辛辨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至慷妙,卻和暖如春僻焚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膝擂。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工虑啤, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人架馋。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓狞山,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叉寂。 傳聞我的和親對象是個殘疾皇子萍启,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容