簡單理解Android事件分發機制(下)——走進源碼解析原理

本篇文章將從源碼的角度解析事件分發機制的詳細內容。關于上篇文章的那些情況迥異的分發處理過程,是如何在源碼中實現的?本篇文章將逐一揭曉。

一、分發機制中三個方法的關系

上篇文章關于dispatchTouchEvent()方法,onInterceptTouchEvent()方法和onTouchEvent ()方法的流程進行了梳理。那么在源碼實現中,三者之間的關系具體是什么樣的?用一段偽代碼來介紹。

/ 點擊事件產生后,會直接調用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
   //代表是否消耗事件
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
    //如果onInterceptTouchEvent()返回true則代表當前View攔截了點擊事件
    //則該點擊事件則會交給當前View進行處理
    //即調用onTouchEvent ()方法去處理點擊事件
      consume = onTouchEvent (ev) ;
    } else {
      //如果onInterceptTouchEvent()返回false則代表當前View不攔截點擊事件
      //則該點擊事件則會繼續傳遞給它的子元素
      //子元素的dispatchTouchEvent()就會被調用,重復上述過程
      //直到點擊事件被最終處理為止
      consume = child.dispatchTouchEvent (ev) ;
    }
    return consume;
   }

上述偽代碼清楚地描述了,事件分發從Activity->ViewGroup->View過程中,三大方法之間的調用關系。

二、Activity中的分發機制

上篇文章,我們介紹到,分發機制是從Activity開始的,當觸摸屏幕時,Activity先感受到,并調用dispatchTouchEvent()方法進行分發。下面就來具體了解這一方法的內容。

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //第一次按下操作時,用戶希望能與設備進行交互,可通過實現該方法
        onUserInteraction();
    }

    //獲取當前Activity的頂層窗口是PhoneWindow,執行其superDispatchTouchEvent()方法
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //當沒有任何view處理時,交由activity的onTouchEvent處理
    return onTouchEvent(ev);
}

可以看到,當Activity向下分發事件,最終沒有任何控件進行處理后,將會交給Activity的onTouchEvent()方法處理。
繼續看superDispatchTouchEvent()方法

public boolean superDispatchTouchEvent(KeyEvent event) {
    return mDecor.superDispatcTouchEvent(event);
}

PhoneWindow的最頂View是DecorView,再交由DecorView處理。而DecorView的父類的父類是ViewGroup,接著調用 ViewGroup.dispatchTouchEvent()方法。

所以Activity中的分發機制簡述為:若不重寫該方法,則調用根ViewGroup的dispatchTouchEvent()方法,進行分發,如果事件沒有任何控件進行處理,則最后返回給Activity的onTouchEvent()方法進行處理。若重寫該方法,不論返回值是false/true,都不會向下進行事件分發,也就是事件停止分發,已經在Activity中消費了。

三、ViewGroup中的分發機制

從Acivity中向下分發,就到達了ViewGroup中的dispatchTouchEvent()方法,下面來具體看看這個方法。
該方法比較復雜,篇幅有限,就截取幾個重要的邏輯片段進行介紹,來解析整個分發流程。

         // 發生ACTION_DOWN事件或者已經發生過ACTION_DOWN,并且將mFirstTouchTarget賦值,才進入此區域,主要功能是攔截器
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            //disallowIntercept:是否禁用事件攔截的功能(默認是false),即不禁用
            //可以在子View通過調用requestDisallowInterceptTouchEvent方法對這個值進行修改,不讓該View攔截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //默認情況下會進入該方法
            if (!disallowIntercept) {
                //調用攔截方法
                intercepted = onInterceptTouchEvent(ev); 
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            // 當沒有觸摸targets,且不是down事件時,開始持續攔截觸摸。
            intercepted = true;
        }

這一段的內容主要是為判斷是否攔截。如果當前事件的MotionEvent.ACTION_DOWN,則進入判斷,調用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果mFirstTouchTarget != null,即已經發生過MotionEvent.ACTION_DOWN,并且該事件已經有ViewGroup的子View進行處理了,那么也進入判斷,調用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果不是以上兩種情況,即已經是MOVE或UP事件了,并且之間的事件沒有對象進行處理,則設置成true,開始攔截接下來的所有事件。這也就解釋了如果子View的onTouchEvent()方法返回false,那么接下來的一些列事件都不會交給他處理。其實這并不是onTouchEvent()方法傲嬌,而是onInterceptTouchEvent()方法沒給他機會,直接攔截了,不給子View機會。如果VieGroup的onInterceptTouchEvent()第一次執行為true,則mFirstTouchTarget = null,則也會使得接下來不會調用onInterceptTouchEvent(),直接將攔截設置為true。

當ViewGroup不攔截事件的時候,事件會向下分發交由它的子View或ViewGroup進行處理。

                    /* 從最底層的父視圖開始遍歷,
                    ** 找尋newTouchTarget,即上面的mFirstTouchTarget
                    ** 如果已經存在找尋newTouchTarget,說明正在接收觸摸事件,則跳出循環。
                    */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);

                        // 如果當前視圖無法獲取用戶焦點,則跳過本次循環
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
                        //如果view不可見,或者觸摸的坐標點不在view的范圍內,則跳過本次循環
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        // 已經開始接收觸摸事件,并退出整個循環。
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        //重置取消或抬起標志位
                        //如果觸摸位置在child的區域內,則把事件分發給子View或ViewGroup
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 獲取TouchDown的時間點
                            mLastTouchDownTime = ev.getDownTime();
                            // 獲取TouchDown的Index
                            if (preorderedList != null) {
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }

                            //獲取TouchDown的x,y坐標
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //添加TouchTarget,則mFirstTouchTarget != null。
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //表示以及分發給NewTouchTarget
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

dispatchTransformedTouchEvent()方法實際就是調用子元素的dispatchTouchEvent()方法。
其中dispatchTransformedTouchEvent()方法的重要邏輯如下:

 if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }

由于其中傳遞的child不為空,所以就會調用子元素的dispatchTouchEvent()。
如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就會被賦值,同時跳出for循環。

//添加TouchTarget,則mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
 //表示以及分發給NewTouchTarget
 alreadyDispatchedToNewTouchTarget = true;

其中在addTouchTarget(child, idBitsToAssign);內部完成mFirstTouchTarget被賦值。
如果mFirstTouchTarget為空,將會讓ViewGroup默認攔截所有操作。
如果遍歷所有子View或ViewGroup,都沒有消費事件。ViewGroup會自己處理事件。

所以ViewGroup中的分發機制簡述為:若子View或ViewGroup不處理MotionEvent.ACTION_DOWN事件,那么接下來的一些列事件都交由ViewGroup處理。若ViewGroup的onInterceptTouchEvent()執行為true,則接下來的所有事件都默認由該ViewGroup執行。若子View或ViewGroup處理MotionEvent.ACTION_DOWN事件,則接下來的事件處理交給誰要看onInterceptTouchEvent()的返回值,如果返回true,則第一個MOVE事件,會變成CANCEL事件,繼續交由原來的子View或ViewGroup處理,接下來的一些列事件都交由ViewGroup執行。如果返回false,則繼續交給原來的子View或ViewGroup處理。

四、View中的分發機制

View中的分發機制就比較簡單了。上面ViewGroup中已經開始調用View.dispatchTouchEvent()方法,下面來具體看一下。

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        //在Down事件之前,如果存在滾動操作則停止。不存在則不進行操作
        stopNestedScroll();
    }
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        //第一個條件默認為true
        //第二個條件mOnTouchListener 不為空
       //第三個條件該條件是判斷當前點擊的控件是否enable 
       //第四個條件onTouch返回值是true
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true; //滿足上述四個條件,已經消費事件,則返回True
        }
        //如果OnTouch()返回false,或者沒滿足其他條件,沒有消費Touch事件則調用OnTouchEvent()
        if (!result && onTouchEvent(event)) { 
            result = true; //onTouchEvent(event)返回true,已經消費事件,則返回True
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // 處理取消或抬起操作
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

針對上述四個條件的判斷,很多View默認是(mViewFlags & ENABLED_MASK) == ENABLED,通過設置OnTouchListener中的onTouch返回true,那么onTouchEvent()方法就不會調用,表明OnTouchListener的優先級高于onTouchEvent。

//手動調用設置
button.setOnTouchListener(new OnTouchListener() {  

  @Override  
  public boolean onTouch(View v, MotionEvent event) {  

      return false;  
  }  
});

如果OnTouchListener中的onTouch返回false,那么會調用onTouchEvent()方法。

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

    // 當View狀態為DISABLED,如果可點擊或可長按,則返回True,即消費事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    //當View狀態為ENABLED,如果可點擊或可長按,則返回True,即消費事件;
    //與前面的的結合,可得出結論:只要view是可點擊或可長按,則消費該事件.
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        setPressed(true, x, y);
                   }

                    if (!mHasPerformedLongPress) {
                        //這是Tap操作,移除長按回調方法
                        removeLongPressCallback();

                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            //調用View.OnClickListener
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                //獲取是否處于可滾動的視圖內
                boolean isInScrollingContainer = isInScrollingContainer();

                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    //當處于可滾動視圖內,則延遲TAP_TIMEOUT,再反饋按壓狀態,用來判斷用戶是否想要滾動。默認延時為100ms
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    //當不再滾動視圖內,則立刻反饋按壓狀態
                    setPressed(true, x, y);
                    checkForLongClick(0); //檢測是否是長按
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                break;

            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);

                if (!pointInView(x, y, mTouchSlop)) {
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        removeLongPressCallback();
                        setPressed(false);
                    }
                }
                break;
        }

        return true;
    }
    return false;
}

只要View的CLICKABLE和LONG_CLICKABLE有一個為true,那么它就會消耗這個事件。
如果View設置了OnClickLisenter,那么performClick方法內部就會調用onClick方法。

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  

    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}

則表明onTouch()優先級高于onTouchEvent(),并高于onClick()。
所以View中的分發機制簡述為:默認情況下,如果View的CLICKABLE和LONG_CLICKABLE有一個為true(默認LONG_CLICKABLE為false,一般可以點擊的View中CLICKABLE為true。),則就會消費該事件,如果都為false,則不會消費該事件,dispatchTouchEvent()方法返回false,交由父控件的循環下一個子View進行同樣操作。如果重寫onTouchEvent()方法,返回false,dispatchTouchEvent()方法返回false,交由父控件的循環下一個子View進行同樣操作。如果重寫onTouchEvent()方法,返回true,則消費該事件。

以上就是源碼解析事件分發的內容。
參考文章:
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
Android事件分發機制

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容