Android 事件分發(fā)的源碼分析

前提條件源碼是基于android 26分析的

基礎知識

什么是觸摸事件

觸摸事件,是 Android 用來描述你的手對屏幕做的事情的最小單元。關鍵詞有兩個:手勢(你的手對屏幕做的事情)、最小單元。
手勢:按下、移動、抬起、點擊事件、長按事件、滑動事件。
最小單元:不可在拆分,比如按下(down)、移動(move)、抬起(up)就是不可再拆分的。而點擊事件、長按事件、滑動事件則是由那些不可再拆分的事件組合起來的。比如點擊事件是由按下、抬起組成的。長按事件是按下并保持一段時間不動。注意一下滑動事件和移動(move)的區(qū)別,滑動事件可以理解為是由若干個“move”組合起來的,系統(tǒng)對觸摸的捕捉是很靈敏的,手指放在屏幕上稍微抖一下,都會產(chǎn)生“move”事件。
這里還有一個取消(cancel)事件,暫時不予理會

MotionEvent 對象

MotionEvent 對象就是對觸摸事件相關信息的封裝
事件類型(包括但不局限于):ACTION_DOWN(按下)、ACTION_ MOVE(移動)、ACTION_ UP(抬起)。

坐標系

坐標還分參考系,有以屏幕作為參考系的坐標值,也有以被觸摸的 View 作為參考系的坐標值。
MotionEvent 的對象內(nèi)的方法與坐標系是存在一定關聯(lián)的具體的可參考下面的圖


View坐標系
一個完整的事件序列

觸摸事件與一個完整的事件序列的區(qū)別

  • 觸摸事件
    最小單元,任何一個對屏幕的操作都是由多個觸摸事件構成的,觸摸事件分別有按下、移動、抬起、取消等
  • 一個完整的事件序列
    ACTION_DOWN(一個)–>ACTION_MOVE(數(shù)量不定)–>ACTION_UP(一個),從 ACTION_DOWN 到 ACTION_UP,稱為一個完整的事件序列,即某一次手指按下到手指抬起算是一個完整的事件序列,下一次手指按下到抬起是另一個完整的事件序列。
    筆者沒有真的考究過 Android 官方是不是真的有“事件序列”這么一個概念性的名詞,只是為了分析源碼方便理解才這樣說的,這也是android“擬人化”行為的一種標識吧,因為從ViewGroup 的源碼里面,確實是可以找到在遇到 ACTION_DOWN 時清除某些標記位(以避免受到前一個事件序列的影響)

為什么要事件分發(fā)

簡單地講,事件分發(fā)就是為了解決“誰來干活”的問題。當一個事件發(fā)生,有超過一個View(或者 ViewGroup)能夠?qū)λM行響應(處理)時,就要確定到底誰來處理這個事件。

View的事件分發(fā)

View的事件分發(fā),是指 View.java 源碼里對觸摸事件進行傳遞的流程

流程圖

View 事件分發(fā)的流程圖是一個主干流程,去掉一些細節(jié)的流程

流程圖如下


View的事件分發(fā)

源碼

public boolean dispatchTouchEvent(MotionEvent event) {
        ....//省略無關的代碼

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //關鍵代碼在這里
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ....

        return result;
    }

可以看到關鍵代碼那里的條件就是流程圖上面的條件
我們再來看onTouchEvent的源碼,這里就是平常設置點擊事件的回調(diào)地方

        public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        ....//省略無關代碼
        //如果view的clickable屬性設置為false也不會響應點擊事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {

                        ....//省略無關代碼
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //這里就是處理點擊按鈕的時間回調(diào)
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        ....//省略無關代碼
                    }
                    mIgnoreNextUpEvent = false;
                    break;

            return true;//返回這個的話就說明事件已經(jīng)消耗了
        }

        return false;//返回這個的話就說明事件拋回給它的父類處理
    }

流程說明

有可能處理事件的有兩個地方,一個是外部設置的 OnTouchListener 監(jiān)聽器,即OnTouchListener的onTouch(),一個是 View 內(nèi)部的處理方法,即 onTouchEvent()。而且外部設置的監(jiān)聽器優(yōu)先獲取事件。

當外部設置的監(jiān)聽器處理了事件(即有設置監(jiān)聽器、View 處于 ENABLE 狀態(tài)、監(jiān)聽器在onTouch()里面返回 true 三者均成立),dispatchTouchEvent() 返回 true,當前 View的 onTouchEvent() 不會觸發(fā)。

如果外部的監(jiān)聽器不處理事件,則dispatchTouchEvent() 的返回值由 View 自己的onTouchEvent()決定。

注意一下,對于上級 ViewGroup(也就是當前 View 的父容器)而言,它只認識子 View的dispatchTouchEvent()方法,不認識另外兩個處理事件的方法。子View的OnTouchListener 和 onTouchEvent() 都是在自己的 dispatchTouchEvent() 里面調(diào)用的,他們兩個會影響 dispatchTouchEvent() 的返回值,但是對于上級 ViewGroup 而言,它只認識 dispatchTouchEvent() 的返回值就足夠了。

ViewGroup 的事件分發(fā)

流程圖

流程如下圖所示:


ViewGroup 的事件分發(fā)

源碼

public boolean dispatchTouchEvent(MotionEvent ev) {
         ....//省略無關代碼
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {

             /**
             * 第一步:對于ACTION_DOWN進行處理(Handle an initial down)
             * 因為ACTION_DOWN是一系列事件的開端,當是ACTION_DOWN時進行一些初始化操作.
             * 從源碼的注釋也可以看出來:清除以往的Touch狀態(tài)(state)開始新的手勢(gesture)
             * cancelAndClearTouchTargets(ev)中有一個非常重要的操作:
             * 將mFirstTouchTarget設置為null!!!!
             * 隨后在resetTouchState()中重置Touch狀態(tài)標識
            */
            // Handle an initial down.
            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.
            // 使用變量intercepted來標記ViewGroup是否攔截Touch事件的傳遞.
            final boolean intercepted;
            //ACTION_DOWN事件或者已經(jīng)找到了事件分發(fā)的目標對象,在一個事件序列是非ACTION_DOWN事件都會進入這里
            //對應流程圖中的第一個判斷分支
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //設置禁止攔截的標記 1.ACTION_DOWN事件與非ACTION_DOWN事件都會走這里
                //因為在其他地方可能調(diào)用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)來影響這個變量的值,
                //所以在一個事件序列中如果多個事件之后確定了是由誰來處理事件,就在這個事件序列中滿足一定條件可以設置這個值
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //再次詢問是否會攔截 1.ACTION_DOWN事件與非ACTION_DOWN事件都會走這里
                    //常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
                    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執(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;
            }
            ....//省略無關代碼
            //不是ACTION_CANCEL并且ViewGroup的攔截標志位intercepted為false(不攔截)
            if (!canceled && !intercepted) {

                ....//省略無關代碼

                //處理ACTION_DOWN事件.這個環(huán)節(jié)比較繁瑣.
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                    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 ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //遍歷viewGroup下所有的子view找到可以消費事件的子view,并且把事件消費的目標賦值
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            ....//省略無關代碼
                            //這個條件是第二次發(fā)生點擊事件(第二個事件序列),這個快速找到事件消費的目標
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                //找到接收事件的目標所以直接跳出
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            /**
                             * 如果上面的if不滿足,當然也不會執(zhí)行break語句.
                             * 于是代碼會執(zhí)行到這里來.
                             * 
                             * 調(diào)用方法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.
                             * 該方法十分重要!!!!在該方法中為一個遞歸調(diào)用,會遞歸調(diào)用dispatchTouchEvent()方法?。。。。。。。。。。。。?!
                             * 在dispatchTouchEvent()中:
                             * 如果子View為ViewGroup并且Touch沒有被攔截那么遞歸調(diào)用dispatchTouchEvent()
                             * 如果子View為View那么就會調(diào)用其onTouchEvent(),這個就不再贅述了.
                             * 
                             * 
                             * 該方法返回true則表示子View消費掉該事件,同時進入該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()
                             *   從而導致mFirstTouchTarget為null.那么該子View就無法繼續(xù)處理ACTION_MOVE事件
                             *   和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!!
                             * 5 注意:
                             *   如果dispatchTransformedTouchEvent()返回true即子View
                             *   的onTouchEvent返回true(即Touch事件被消費)那么就滿足該if條件.
                             *   從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!!
                             * 6 小結:
                             *   對于此處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īng)Q定了ViewGroup是否
                             *   處理Touch事件.這一點在下面的代碼中很有體現(xiàn).
                             *   
                             * 
                            */

                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //這里是對第一次事件消費的目標賦值(mFirstTouchTarget的賦值的地方)
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    //該條件表示沒有找到子view接收事件并且之前的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;
                    }
                }
                    
            }
        }

            /**
             * 分發(fā)Touch事件至target
             * 
             * 經(jīng)過上面對于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況:
             * 1 mFirstTouchTarget為null
             * 2 mFirstTouchTarget不為null
             * 
             * 當然如果不是ACTION_DOWN就不會經(jīng)過上面較繁瑣的流程
             * 而是從此處開始執(zhí)行,比如ACTION_MOVE和ACTION_UP
            */
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //簡要分析
                //1.子view不處理事件會執(zhí)行這里
                //2.無法找到事件消費的目標
                //都是走super.dispatchTouchEvent(event);
                /**
                 * 詳細分析
                 * 情況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()的源碼分析
                 * 
                 * 
                */
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                /**
                 * 情況2:mFirstTouchTarget不為null即找到了可以消費Touch事件的子View  多數(shù)情況下后續(xù)Touch事件可以傳遞到該子View,
                 * 但是在事件還沒確定是viewGroup還是view處理時會一直流經(jīng)兩個的事件流中
                */
                // 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事件繼續(xù)傳遞給目標子組件進行處理,依然是遞歸調(diào)用dispatchTransformedTouchEvent()
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        //對于非ACTION_DOWN事件如果中途攔截了(intercepted = true),這個條件會把mFirstTouchTarget置為null
                        //等下一個touch事件就會把事件給viewGroup自己處理
                        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)
            */
            // 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;
    }
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容