Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android事件分發詳解(三)——ViewGroup的dispatchTouchEvent()源碼學習

Android事件分發詳解(三)——ViewGroup的dispatchTouchEvent()源碼學習

編輯:關於Android編程

package cc.aa;

import android.os.Environment;
import android.view.MotionEvent;
import android.view.View;

public class UnderstandDispatchTouchEvent {
    /**
     * dispatchTouchEvent()源碼學習及其注釋
     * 常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
     * 在這個鏈條中dispatchTouchEvent()是處在鏈首的位置當然也是最重要的.
     * 在dispatchTouchEvent()決定了Touch事件是由自己的onTouchEvent()處理
     * 還是分發給子View處理讓子View調用其自身的dispatchTouchEvent()處理.
     * 
     * 
     * 其實dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()的關系
     * 在dispatchTouchEvent()方法的源碼中體現得很明顯.
     * 比如dispatchTouchEvent()會調用onInterceptTouchEvent()來判斷是否要攔截.
     * 比如dispatchTouchEvent()會調用dispatchTransformedTouchEvent()方法且在該方法中遞歸調用
     * dispatchTouchEvent();從而會在dispatchTouchEvent()裡最終調用到onTouchEvent()
     * 
     * 
     * 
     * 重點關注:
     * 1 子View對於ACTION_DOWN的處理十分重要!!!!!
     *   ACTION_DOWN是一系列Touch事件的開端,如果子View對於該ACTION_DOWN事件在onTouchEvent()中返回了false即未消費.
     *   那麼ViewGroup就不會把後續的ACTION_MOVE和ACTION_UP派發給該子View.在這種情況下ViewGroup就和普通的View一樣了,
     *   調用該ViewGroup自己的dispatchTouchEvent()從而調用自己的onTouchEvent();即不會將事件分發給子View.
     *   詳細代碼請參見如下代碼分析.
     *   
     * 2 為什麼子view對於Touch事件處理返回true那麼其上層的ViewGroup就無法處理Touch事件了?????
     *   這個想必大家都知道了,因為該Touch事件被子View消費了其上層的ViewGroup就無法處理該Touch事件了.
     *   那麼在源碼中的依據是什麼呢??請看下面的源碼分析
     *   
     * 參考資料:
 www.2cto.com
     *   Thank you very much
     */
	
    @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進行處理(Handle an initial down)
             * 因為ACTION_DOWN是一系列事件的開端,當是ACTION_DOWN時進行一些初始化操作.
             * 從源碼的注釋也可以看出來:清除以往的Touch狀態(state)開始新的手勢(gesture)
             * cancelAndClearTouchTargets(ev)中有一個非常重要的操作:
             * 將mFirstTouchTarget設置為null!!!!
             * 隨後在resetTouchState()中重置Touch狀態標識
             */
            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)
			 * 在dispatchTouchEvent(MotionEventev)這段代碼中
			 * 使用變量intercepted來標記ViewGroup是否攔截Touch事件的傳遞.
			 * 該變量在後續代碼中起著很重要的作用.
			 */
            final boolean intercepted;
			// 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經找到能夠接收touch事件的目標組件)時if成立
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            	//判斷disallowIntercept(禁止攔截)標志位
				//因為在其他地方可能調用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
				//從而禁止執行是否需要攔截的判斷(有點拗口~其實看requestDisallowInterceptTouchEvent()方法名就可明白)
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //當沒有禁止攔截判斷時(即disallowIntercept為false)調用onInterceptTouchEvent(ev)方法
                if (!disallowIntercept) {
                	//既然disallowIntercept為false那麼就調用onInterceptTouchEvent()方法將結果賦值給intercepted
                	//常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
                	//其實在這就是一個體現,在dispatchTouchEvent()中調用了onInterceptTouchEvent()
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                	 //當禁止攔截判斷時(即disallowIntercept為true)設置intercepted = false
                    intercepted = false;
                }
            } else {
            	//當事件不是ACTION_DOWN並且mFirstTouchTarget為null(即沒有Touch的目標組件)時
            	//設置 intercepted = true表示ViewGroup執行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;

            
            /**
             * 第四步:事件分發(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的攔截標志位intercepted為false(不攔截)
            if (!canceled && !intercepted) {
            	//處理ACTION_DOWN事件.這個環節比較繁瑣.
                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) {
                    	// 依據Touch坐標尋找子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.
                            	// 既然已經找到了,所以執行break跳出for循環
                                // 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不滿足,當然也不會執行break語句.
                             * 於是代碼會執行到這裡來.
                             * 
                             * 調用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做
                             * 遞歸處理(也就是遍歷該子View的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.
                             * 將Touch事件傳遞給特定的子View.
                             * 該方法十分重要!!!!在該方法中為一個遞歸調用,會遞歸調用dispatchTouchEvent()方法!!!!!!!!!!!!!!
                             * 在dispatchTouchEvent()中:
                             * 如果子View為ViewGroup並且Touch沒有被攔截那麼遞歸調用dispatchTouchEvent()
                             * 如果子View為View那麼就會調用其onTouchEvent(),這個就不再贅述了.
                             * 
                             * 
                             * 該方法返回true則表示子View消費掉該事件,同時進入該if判斷.
                             * 滿足if語句後重要的操作有:
                             * 1 給newTouchTarget賦值
                             * 2 給alreadyDispatchedToNewTouchTarget賦值為true.
                             *   看這個比較長的英語名字也可知其含義:已經將Touch派發給新的TouchTarget
                             * 3 執行break.
                             *   因為該for循環遍歷子View判斷哪個子View接受Touch事件,既然已經找到了
                             *   那麼就跳出該for循環.
                             * 4 注意:
                             *   如果dispatchTransformedTouchEvent()返回false即子View
                             *   的onTouchEvent返回false(即Touch事件未被消費)那麼就不滿足該if條件,也就無法執行addTouchTarget()
                             *   從而導致mFirstTouchTarget為null.那麼該子View就無法繼續處理ACTION_MOVE事件
                             *   和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!!
                             * 5 注意:
                             *   如果dispatchTransformedTouchEvent()返回true即子View
                             *   的onTouchEvent返回true(即Touch事件被消費)那麼就滿足該if條件.
                             *   從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!!
                             * 6 小結:
                             *   對於此處ACTION_DOWN的處理具體體現在dispatchTransformedTouchEvent()
                             *   該方法返回boolean,如下:
                             *   true---->事件被消費----->mFirstTouchTarget!=null
                             *   false--->事件未被消費---->mFirstTouchTarget==null
                             *   因為在dispatchTransformedTouchEvent()會調用遞歸調用dispatchTouchEvent()和onTouchEvent()
                             *   所以dispatchTransformedTouchEvent()的返回值實際上是由onTouchEvent()決定的.
                             *   簡單地說onTouchEvent()是否消費了Touch事件(true or false)的返回值決定了dispatchTransformedTouchEvent()
                             *   的返回值!!!!!!!!!!!!!從而決定了mFirstTouchTarget是否為null!!!!!!!!!!!!!!!!從而進一步決定了ViewGroup是否
                             *   處理Touch事件.這一點在下面的代碼中很有體現.
                             *   
                             * 
                             */
                            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();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }

                    
                    /**
                     * 該if條件表示:
                     * 經過前面的for循環沒有找到子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;
                    }
                }
            }

            
            
            /**
             * 分發Touch事件至target(Dispatch to touch targets)
             * 
             * 經過上面對於ACTION_DOWN的處理後mFirstTouchTarget有兩種情況:
             * 1 mFirstTouchTarget為null
             * 2 mFirstTouchTarget不為null
             * 
             * 當然如果不是ACTION_DOWN就不會經過上面較繁瑣的流程
             * 而是從此處開始執行,比如ACTION_MOVE和ACTION_UP
             */
            if (mFirstTouchTarget == null) {
            	/**
            	 * 情況1:mFirstTouchTarget為null
            	 * 
            	 * 經過上面的分析mFirstTouchTarget為null就是說Touch事件未被消費.
            	 * 即沒有找到能夠消費touch事件的子組件或Touch事件被攔截了,
            	 * 則調用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
            	 * 即子View沒有消費Touch事件,那麼子View的上層ViewGroup才會調用其onTouchEvent()處理Touch事件.
            	 * 在源碼中的注釋為:No touch targets so treat this as an ordinary view.
            	 * 也就是說此時ViewGroup像一個普通的View那樣調用dispatchTouchEvent(),且在dispatchTouchEvent()
            	 * 中會去調用onTouchEvent()方法.
            	 * 具體的說就是在調用dispatchTransformedTouchEvent()時第三個參數為null.
            	 * 第三個參數View child為null會做什麼樣的處理呢?
            	 * 請參見下面dispatchTransformedTouchEvent()的源碼分析
            	 * 
            	 * 這就是為什麼子view對於Touch事件處理返回true那麼其上層的ViewGroup就無法處理Touch事件了!!!!!!!!!!
            	 * 這就是為什麼子view對於Touch事件處理返回false那麼其上層的ViewGroup才可以處理Touch事件!!!!!!!!!!
            	 * 
            	 */
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
            } else {
            	/**
            	 * 情況2:mFirstTouchTarget不為null即找到了可以消費Touch事件的子View且後續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) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                        //對於非ACTION_DOWN事件繼續傳遞給目標子組件進行處理,依然是遞歸調用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.
             * 在此主要的操作是還原狀態
             */
            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()中調用dispatchTransformedTouchEvent()將事件分發給子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.
     * 
     * 在此請著重注意第三個參數:View child
     * 在dispatchTouchEvent()中多次調用了dispatchTransformedTouchEvent(),但是有時候第三個參數為null,有時又不是.
     * 那麼這個參數是否為null有什麼區別呢?
     * 在如下dispatchTransformedTouchEvent()源碼中可見多次對於child是否為null的判斷,並且均做出如下類似的操作:
     * if (child == null) {
     *       handled = super.dispatchTouchEvent(event);
     *    } else {
     *       handled = child.dispatchTouchEvent(event);
     * }
     * 這個代碼是什麼意思呢??
     * 當child == null時會將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
     * 即super.dispatchTouchEvent(event)正如源碼中的注釋描述的一樣:
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     * 當child != null時會調用該子view(當然該view可能是一個View也可能是一個ViewGroup)的dispatchTouchEvent(event)處理.
     * 即child.dispatchTouchEvent(event);
     * 
     * 
     */
    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;
    }
    
    
    
    
    
    

}
上一篇/kf/201412/365596.html
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved