Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android View事件分發機制

Android View事件分發機制

編輯:關於Android編程

兩個流程

向下分發
向下分發:Android接收到觸屏事件,由Activity開始逐層向下傳遞,直到傳遞給一個View,或者ViewGroup攔截這個事件為止。

分發和傳遞的方法分別為dispatchTouchEvent,onInterceptTouchEvent
其中dispatchTouchEvent,View、Activity、ViewGroup都有
onInterceptTouchEvent 只有ViewGroup有

事件傳遞給ViewGroup時候,ViewGroup會調用dispatchTouchEvent,
dispatchTouchEvent又會調用onInterceptTouchEvent方法
如果onInterceptTouchEvent返回true(攔截)那麼,這個事件就交給這個ViewGroup來消耗,如果不攔截,向下找子View看哪個消耗(調用onTouchEvent)

注意:onInterceptTouchEvent 不是一直調用的
觸屏事件可以是一些列事件 dowm->move->move…move->up
如果這個事件是由這個ViewGroup處理的那麼在之後不會調用onInterceptTouchEvent
如果有個標志位FLAG_DISALLOW_INTERCEPT 設置為true 也不會再次調用
onInterceptTouchEvent 而是直接把其賦值為false

向上傳遞
當一個事件傳到最底層的View的時候會調用onTouchEvent,如果這個onTouchEvent返回false,就交給上層處理,一直往上直到Activity

注意:
onTouchListener的onTouch方法優先級比onTouchEvent高,會先觸發。

假如onTouch方法返回false會接著觸發onTouchEvent,反之onTouchEvent方法不會被調用。

內置諸如click事件的實現等等都基於onTouchEvent,假如onTouch返回true,這些事件將不會被觸發。

public boolean dispatchTouchEvent(MotionEvent event){  
    ... ...  
    if(onFilterTouchEventForSecurity(event)){  
        ListenerInfo li = mListenerInfo;  
        if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
            && li.mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        if(onTouchEvent(event)){  
            return true;  
        }  
    }  
    ... ...  
    return false;  
}  

dispatchTouchEvent分析

import android.view.MotionEvent;    
import android.view.View;    

public class ZeroDispatchTouchEvent {    
    /**  
     * 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事件了.  
     *   那麼在源碼中的依據是什麼呢??請看下面的源碼分析  
     */    

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

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