ViewGroup 簡析dispatchTouchEvent返回值

金九銀十的換工作季又到了。希望那些每天都進(jìn)步的攻城獅們都能擁有一個(gè)美麗而又多金的好工作。
事件分發(fā)機(jī)制一直是面試官喜歡問的題目,所以我們有必要徹底搞清楚。搞清楚了它不僅對我們現(xiàn)在的工作有益,對我們換新工作也百益無一害。
看了網(wǎng)上很多大牛寫的文章,自己同時(shí)也一一對著源碼看了,都說好記性不如爛筆頭,這才有了這篇文章的由來。

首先為了不耽誤你的時(shí)間,我要事先說個(gè)明,我這次分析是從Activity開始,platform-29,不是從Framework或者更底層說起,如果大家有興趣對事件更深層的了解,可以去看看下面這篇文章,它從整個(gè)系統(tǒng)層面進(jìn)行了詳細(xì)的闡述。

https://juejin.cn/post/6844903926446161927

先解釋下幾個(gè)名詞
  • 事件序列:Android的每一次按下都會是一連串的事件,而不是單個(gè)的,序列主要包括一個(gè)按下事件,0個(gè)或N個(gè)移動事件,一個(gè)抬起事件
  • 消費(fèi)事件:當(dāng)前view處理完事件,返回值為true,返回false代表不消費(fèi)

1 首先來張全局的流程圖

事件分發(fā)機(jī)制.jpeg
看上面的圖你有沒有發(fā)現(xiàn)有個(gè)邏輯沒有講,就是dispatchTouchEvent返回值代表啥意思?好的,我就從這個(gè)切入點(diǎn)講講。

2 對上面的圖解析

先來看Activity的分發(fā)代碼

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

我現(xiàn)在寫的每一句都不是廢話,如果你跟不上,請重復(fù)觀看代碼,我保證你一定跟得上。
從上面我們可以看出如果getWindow().superDispatchTouchEvent()這個(gè)方法返回true,它也就返回true了。否則則調(diào)用自己的onTouchEvent()方法。

這個(gè)東東getWindow().superDispatchTouchEvent()又是誰?你要是敢問,我就直接一個(gè)眼神給你,然后大家心照不宣。好吧,本著服務(wù)大眾的心態(tài),你看做人就得心態(tài)好。心態(tài)好了,吃嘛嘛香,擼碼碼順 ╰( ̄▽ ̄)╭
看過幾篇事件分發(fā)機(jī)制文章的人都應(yīng)該知道getWindow其實(shí)是PhoneWindow,

getWindow().superDispatchTouchEvent()源碼

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

看到如此清涼的代碼,是不是感覺好像在7月炎熱的太陽下喝了一瓶透心涼的-雪碧。
真是簡單的不能再簡單了。源碼都這么簡單多好??!我們接著往下跟

mDecor.superDispatchTouchEvent()源碼

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

有沒有感覺到幸福二重奏?又是讓人豁然開朗的一行代碼
這代碼真帶勁,越看越上癮。
到這里,你應(yīng)該能猜到下個(gè)代碼會走到哪里了吧?Right,你猜對了,mDecor是一個(gè)DecorView---FrameLayout的子類。

DecorView源碼

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

于是 ViewGroup的dispatchTouchEvent就進(jìn)入了我們的視野。
由于方法過長,我對其進(jìn)行了裁剪

ViewGroup.dispatchTouchEvent()源碼

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
     ...
     
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 如果是按下事件,重置一下mFirstTouchTarget這個(gè)對象為null
                cancelAndClearTouchTargets(ev);
               // 如果是按下事件,重置 攔截Flag
                resetTouchState();
            }

            // 按下或者找到了消費(fèi)事件的view
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
//這里先判斷是否設(shè)置了禁用攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//沒有設(shè)置禁用攔截情況下,intercepted取決于onInterceptTouchEvent()返回值
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
//不是事件流開始的 ACTION_DOWN,也沒有事件流的消費(fèi)組件,那么直接攔截。
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // 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;
            if (!canceled && !intercepted) {// 不取消,不攔截,就分發(fā)
        
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 就是這里分發(fā),看子View是否消費(fèi)
                                // 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();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);// 將消費(fèi)事件流的子View的父View(當(dāng)前ViewGroup)記錄在消費(fèi)的鏈表頭
                                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();
                    }
                    //  dispatchTransformedTouchEvent方法返回false,意味著子View也不消費(fèi)
                    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.pointerIdBits |= idBitsToAssign;
                    }
                }// DOWN 事件的處理結(jié)束
            }

            // Dispatch to touch targets.
            // mFirstTouchTarget賦值是在通過addTouchTarget方法獲取的;
            // 只有處理ACTION_DOWN事件,才會進(jìn)入addTouchTarget方法。
            // 這也正是當(dāng)View沒有消費(fèi)ACTION_DOWN事件,則不會接收其他MOVE,UP等事件的原因
            if (mFirstTouchTarget == null) { // 子View不消費(fèi),交給自己處理
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);// path 1
            } else {
                // 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) {// path 2
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;// path 3
                        }
            
            }
...
        return handled;
    }

重要部分我都加了注釋,依然還有不明白的可以在評論區(qū)at我,我知無不言言無不盡。
重點(diǎn)講一下我加的3個(gè)path,因?yàn)閺牧鞒虉D里看不出這三個(gè)分支怎么判斷的。這里的邏輯判斷比較緊湊,防止你跟不上我的車,請先深呼吸一口氣,Ready ?Go !

首先來看

  • path 1 :當(dāng)沒有找到消費(fèi)的子view時(shí),handled 的值為dispatchTransformedTouchEvent 返回的值,這個(gè)方法的第三個(gè)參數(shù)傳的是null值,我們跳進(jìn)這個(gè)方法可以看到,如果child為null,那么就返回super.dispatchTouchEvent(transformedEvent)的值,這個(gè)值一定為false,為什么呢?看咱們的前提---當(dāng)沒有找到消費(fèi)的子view,沒有消費(fèi)說明ViewGroup或子view的onTouchEvent一定返回的是false,所以整個(gè)dispatchTouchEvent()返回的值為false,然后它把結(jié)果依次往上傳遞,最后到decorView,然后傳到Activity,getWindow().superDispatchTouchEvent()這里返回了false,那么就會走Activity的onTouchEvent,這個(gè)方法啥也沒干,它也直接返回了false,事件結(jié)束。

  • path 2和path 3 :找到了消費(fèi)事件的view后, handled能不能為true,關(guān)鍵就看else分之了,handled的值如果為true,我們就反推出dispatchTransformedTouchEvent()這個(gè)方法也返回true,關(guān)鍵就看這個(gè)方法到底返回的值是true還是false呢?這個(gè)方法又依賴于View的 dispatchTouchEvent 方法,索性我們就看看它到底藏著什么玄機(jī)?
    為了方便分析,我把不同類的方法放一塊,我會標(biāo)出類名

//ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
...
      
        // Perform any necessary transformations and dispatch.
        if (child == null) {//如果子view為空,返回父view的dispatchTouchEvent方法的返回值
            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());
            }
          //如果子view不為空,返回子view的dispatchTouchEvent方法的返回值
            handled = child.dispatchTouchEvent(transformedEvent);
        }
      
        return handled;
    }

// View.java
public boolean dispatchTouchEvent(MotionEvent event) {
    ...

    // mOnTouchListener.onTouch優(yōu)先于onTouchEvent。
    if (onFilterTouchEventForSecurity(event)) {
        //當(dāng)存在OnTouchListener,且視圖狀態(tài)為ENABLED時(shí),調(diào)用onTouch()方法
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true; //如果已經(jīng)消費(fèi)事件,則返回True
        }
        //如果OnTouch()沒有消費(fèi)Touch事件則調(diào)用OnTouchEvent()
        if (!result && onTouchEvent(event)) { // [見小節(jié)2.5.1]
            result = true; //如果已經(jīng)消費(fèi)事件,則返回True
        }
    }
  
    return result;
}

從view的 dispatchTouchEvent方法可以看出,如果消費(fèi)了事件,一定是返回true的,這樣 handled = child.dispatchTouchEvent(transformedEvent);通過這行代碼handled也為true了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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