Android中的事件分發(fā)機(jī)制

Android中的事件分發(fā)機(jī)制

在Android開(kāi)發(fā)中二驰,事件的分發(fā)機(jī)制是一塊比較重要的知識(shí)體系,了解并熟悉Android中的事件分發(fā)機(jī)制有助于分析各種點(diǎn)擊滑動(dòng)失效問(wèn)題遵倦,同時(shí)也能更好的去擴(kuò)展控件的事件功能和開(kāi)發(fā)自定義控件署鸡。這篇博客主要就是分析Android中的事件分發(fā)。

首先料祠,來(lái)一張圖說(shuō)明Android中事件是怎樣從Activity怎樣一步一步傳遞到View的

Android事件分發(fā)機(jī)制圖解.png

針對(duì)上圖需要說(shuō)明幾點(diǎn):

  1. 對(duì)于圖中的dispatchTouchEvent()方法,走return true/false線路時(shí)澎羞,表示是開(kāi)發(fā)者重寫的該方法;走super時(shí)敛苇,表示是開(kāi)發(fā)者沒(méi)有重寫妆绞,或者是重寫時(shí)調(diào)用super.dispatchTouchEvent()方法,表示是調(diào)用父類的處理方式枫攀;
  2. 圖中的事件分發(fā)流程只是針對(duì)DOWN(按下)事件括饶,MOVE(移動(dòng))和UP(抬起)事件在后面做分析;

針對(duì)上圖做一個(gè)簡(jiǎn)單的分析:

  1. 從圖中可以看到来涨,事件的分發(fā)過(guò)程可以簡(jiǎn)單的分為三層图焰,Activity層、ViewGroup層和View層蹦掐,在Activity和View層當(dāng)中與事件相關(guān)的方法主要是dispatchTouchEvent()方法和onTouchEvent()方法技羔,而在ViewGroup中還多了一個(gè)onInterceptTouchevent()方法,表示事件攔截的方法卧抗;
  2. Activity獲取到事件之后藤滥,如果開(kāi)發(fā)者重寫了Activity中的dispatchTouchEvent()方法并返回了true或false,事件就會(huì)被消費(fèi)社裆,否則就會(huì)調(diào)用系統(tǒng)的處理方式拙绊,走super,也就是走到了ViewGroup的dispatchTouchEvent()方法,至于為什么會(huì)從Activity的dispatchTouchEvent()走到ViewGroup中的方法标沪,后面會(huì)有分析榄攀;
  3. 事件到了ViewGroup組件上,同樣金句,如果開(kāi)發(fā)者重寫了ViewGroup的dispatchTouchEvent()方法并返回了true檩赢,事件就會(huì)被消費(fèi);如果返回的false趴梢,那么事件就會(huì)直接返回給Activity的onTouchEvent()方法漠畜,讓Activity的onTouchEvent()方法來(lái)處理;如果開(kāi)發(fā)者沒(méi)有重寫坞靶,就會(huì)走super憔狞,那么就會(huì)調(diào)用ViewGroup的onInterceptTouchevent()方法(這個(gè)方法表示事件攔截的方法,返回true表示攔截事件彰阴,返回false表示不攔截事件瘾敢,系統(tǒng)默認(rèn)是返回false,不攔截事件)尿这;
  4. 到了ViewGroup的onInterceptTouchevent()方法簇抵,如果開(kāi)發(fā)者重寫了ViewGroup的onInterceptTouchevent()方法并返回true,那么就會(huì)調(diào)用ViewGroup的onTouchEvent()方法射众,讓ViewGroup的onTouchEvent()方法來(lái)處理事件碟摆;
  5. 在ViewGroup的onTouchEvent()方法中如果返回true,表示消費(fèi)事件叨橱,返回false典蜕,表示不處理事件,事件將會(huì)傳遞到Activity的onTouchEvent()方法處理罗洗;如果在ViewGroup的onInterceptTouchevent()方法中返回false愉舔,不攔截事件,那么事件就會(huì)傳遞到View中伙菜;
  6. 事件到了View中轩缤,View就會(huì)調(diào)用自己的dispatchTouchEvent()方法,同樣贩绕,如果開(kāi)發(fā)者重寫了dispatchTouchEvent()方法并返回了true火的,事件就會(huì)被消費(fèi);如果返回的false淑倾,那么事件就會(huì)直接返回給ViewGroup的onTouchEvent()方法卫玖,讓ViewGroup的onTouchEvent()方法來(lái)處理;
  7. 如果開(kāi)發(fā)者在View中沒(méi)有重寫dispatchTouchEvent()方法踊淳,就會(huì)走super假瞬,系統(tǒng)會(huì)調(diào)用View的onTouchEvent()方法處理事件陕靠,如果在onTouchEvent()方法中返回了true,表示消費(fèi)了事件脱茉,如果返回了false剪芥,那么事件就會(huì)返回到上一層,也就是ViewGroup的onTouchEvent()方法琴许,讓ViewGroup中的onTouchEvent()方法處理事件税肪。

在上面的說(shuō)明中,不僅說(shuō)到了事件從Activity分發(fā)到View的過(guò)程榜田,其實(shí)益兄,事件在ViewGroup和View中的分發(fā)傳遞過(guò)程也已經(jīng)說(shuō)完了,下面這用兩張更加直觀的表示出了ViewGroup和View的事件分發(fā)過(guò)程

ViewGroups事件分發(fā)過(guò)程圖解.png

View事件分發(fā)過(guò)程圖解.png

對(duì)于這兩張圖就不做更多的說(shuō)明了箭券,在圖中已經(jīng)表示的非常清楚了净捅。同時(shí)在對(duì)第一張圖片進(jìn)行說(shuō)明的部分也已經(jīng)涉及到了。

下面對(duì)于Android中DOWN事件的分發(fā)過(guò)程做一個(gè)簡(jiǎn)單的總結(jié):

  1. 對(duì)于ViewGroup和View的disatchTouchEvent()和onTouchEvent()方法辩块,return true表示處理事件蛔六,事件終結(jié);return false表示不處理事件废亭,讓事件回傳到上一層的onTouchEvent()方法中国章,也就是父控件中的onTouchEvent()方法中;
  2. 對(duì)于Activity的disatchTouchEvent()方法豆村,如果沒(méi)有重寫液兽,就會(huì)通過(guò)調(diào)用ViewGroup的disatchTouchEvent()方法開(kāi)始分發(fā)事件,如果重寫了掌动,那么不管返回true還是false都會(huì)消費(fèi)事件四啰,不在將事件往下分發(fā);
  3. 對(duì)于dispatchTouchEvent()方法坏匪,如果開(kāi)發(fā)者不重寫,就會(huì)走系統(tǒng)中的默認(rèn)實(shí)現(xiàn)撬统,在ViewGroup中會(huì)調(diào)用ViewGroup的onInterceptTouchEvent()方法适滓,而在View中會(huì)直接把事件分發(fā)給View的onTouchEvent()方法處理;
  4. 對(duì)于ViewGroup而言恋追,如果ViewGroup要自己處理事件凭迹,需要重寫onInterceptTouchEvent()方法并且返回true,這樣才會(huì)終止事件的傳遞苦囱,并調(diào)用ViewGroup的onTouchEvent()方法處理事件嗅绸,否則調(diào)用系統(tǒng)onInterceptTouchEvent()方法,系統(tǒng)默認(rèn)的返回值是false撕彤,不處理事件將事件傳遞下去鱼鸠;
  5. 對(duì)于View而言猛拴,在View中是沒(méi)有onInterceptTouchEvent()方法的,因?yàn)樗麤](méi)有孩子控件蚀狰,不需要攔截愉昆,系統(tǒng)在dispatchTouchEvent()方法中默認(rèn)會(huì)把事件分發(fā)給View的onTouchEvent()方法處理;
  6. 在Android中麻蹋,最開(kāi)始獲取到事件的是Activity跛溉,然后由Activity的dispatchTouchEvent()方法開(kāi)始分發(fā)事件;
  7. 如果一個(gè)事件由Activity開(kāi)始下發(fā)扮授,但是所有的控件都不處理事件芳室,最終就會(huì)回到Activity的onTouchEvent()方法,如果Activity也不消費(fèi)事件刹勃,那么這個(gè)事件就丟失了堪侯。

上面說(shuō)到了DOWN事件的分發(fā)、傳遞以及處理過(guò)程深夯。

下面就的說(shuō)一下MOVE和UP事件的傳遞過(guò)程:

在Android的事件中抖格,最重要也是最復(fù)雜的一個(gè)事件就是DOWN事件,對(duì)于MOVE和UP事件咕晋,如果一個(gè)控件不能處理DOWN事件雹拄,那么在MOVW和UP的時(shí)候Android系統(tǒng)是不會(huì)將事件分發(fā)給這個(gè)控件的,也就是說(shuō)掌呜,如果一個(gè)控件處理不了DOWN事件滓玖,那么他就接收不到了MOVE和UP事件了,更別說(shuō)處理质蕉;相反势篡,如果一個(gè)控件能處理DOWN事件,那么系統(tǒng)就會(huì)把MOVE和UP事件也交給他處理模暗。下面這張表示了DOWN事件和MOVE禁悠、UP事件的一個(gè)傳遞過(guò)程:

Android中事件傳遞過(guò)程.png

在上圖中,事件由容器控件ViewGroup1傳遞下來(lái)兑宇,如果是DOWN事件碍侦,那么就會(huì)按照上面所說(shuō)的過(guò)程一步一步傳遞,從容器控件ViewGroup1中傳遞到容器控件ViewGroup2在到View中隶糕,然后View也不消費(fèi)事件瓷产,又回傳到容器控件ViewGroup2中,容器控件ViewGroup2的onTouchEvent()方法消費(fèi)了事件枚驻,DOWN事件終結(jié)濒旦;但是如果再次從容器控件ViewGroup1分發(fā)下拉了MONE或者UP事件時(shí),容器控件ViewGroup2就會(huì)直接將事件交給自己的onTouchEvent()方法處理再登,而不會(huì)將事件在次向DWON事件一樣傳遞給View了尔邓。

到這里晾剖,Android中的事件分發(fā)就基本上說(shuō)完了。只是在這里還有2點(diǎn)要簡(jiǎn)單的進(jìn)行說(shuō)明一下:

  1. 在第一張圖中有2個(gè)方法(dispatchTouchEvent()方法和onTouchEvent()方法)都可以消費(fèi)事件铃拇,

    1. 如果一個(gè)DOWN事件是在某個(gè)控件的dispatchTouchEvent()方法中消費(fèi)掉的钞瀑,那么MOVE和UP事件傳到這里就會(huì)終止,不會(huì)再往下傳了慷荔;
    2. 如果一個(gè)DOWN事件是在某個(gè)控件的onTouchEvent()方法中消費(fèi)的雕什,那么MOVE和UP事件就依然會(huì)被傳遞到該控件的onTouchEvent()方法中處理。
  2. Android中的控件還有一個(gè)setOnTouchListener()方法

     view.setOnTouchListener(new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
             return false;
         }
     });
    

需要注意:

  1. onTouch()方法優(yōu)越于onTouchEvent()方法先執(zhí)行显晶;
  2. 如果onTouch()返回true贷岸,那么就不會(huì)執(zhí)行onTouchEvent()方法。

從源碼的角度來(lái)了解一下Android下的事件分發(fā)機(jī)制

同時(shí)也回答上文中留下的一個(gè)問(wèn)題:Activity在做事件分發(fā)時(shí)為什么會(huì)調(diào)用ViewGroup中的dispatchTouchEvent()方法磷雇?

首先看到Activity中的dispatchTouchEvent()方法源碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 如果是ACTION_DOWN事件會(huì)走這個(gè)語(yǔ)句偿警,onUserInteraction()這個(gè)方法在系統(tǒng)中是空實(shí)現(xiàn)
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    
    /**
     * 主要看一下這行代碼
     * getWindow()表示獲取Window的子類PhoneWindow對(duì)象
     * 也就是說(shuō)調(diào)用PhoneWindow中的superDispatchTouchEvent(ev)方法,判斷是否有控件處理事件
     */
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }

    // 如果沒(méi)有控件能處理事件唯笙,就走這一行代碼螟蒸,調(diào)用Activity的onTouchEvent()方法處理事件
    return onTouchEvent(ev);
}

接著進(jìn)入到PhoneWindow中的,查看superDispatchTouchEvent(ev)這個(gè)方法:

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

在PhoneWindow類的superDispatchTouchEvent(ev)方法中崩掘,直接調(diào)用了mDecor對(duì)象的superDispatchTouchEvent(ev)方法七嫌,mDecore其實(shí)就是繼承至FrameLayout的DecorView的對(duì)象。在《Activity的組成》這篇博客中貼出了DecorView類的定義源碼苞慢。

接著查看類中的superDispatchTouchEvent(ev)這個(gè)方法:

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

只有一句代碼诵原,super.dispatchTouchEvent(event),調(diào)用父類的dispatchTouchEvent(event)方法挽放,也就是FrameLayout的dispatchTouchEvent(event)方法绍赛,查看FrameLayout類會(huì)發(fā)現(xiàn)FrameLayout并沒(méi)有重寫dispatchTouchEvent(event)方法,那么就是使用的ViewGroup中的dispatchTouchEvent(event)方法辑畦。

到這里也就完全說(shuō)明了Activity在做事件分發(fā)時(shí)調(diào)用的是ViewGroup中的dispatchTouchEvent()方法吗蚌。

查看ViewGroup中的dispatchTouchEvent()方法:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    boolean handled = false;
    // 過(guò)濾觸摸安全策略,如果是false(窗口或控件被遮住了時(shí))纯出,直接跳出觸摸事件
    // 如果應(yīng)該分發(fā)事件(調(diào)用onTouch()或onTouchEvdent()方法)蚯妇,則返回True;如果應(yīng)該刪除事件,則返回false
    if (onFilterTouchEventForSecurity(ev)) {
        ...
        /**
         * 如果是DOWN事件就先將mFirstTouchTarget設(shè)置為null潦刃,
         * 然后在resetTouchState()方法中重置狀態(tài)
         */
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
         
        // 定義變量intercepted標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
        final boolean intercepted;
        // 事件為ACTION_DOWN或者mFirstTouchTarget不為null(有控件消費(fèi)touch事件)
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            //判斷disallowIntercept(禁止攔截)標(biāo)志位
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //當(dāng)沒(méi)有禁止攔截時(shí)
            if (!disallowIntercept) {
                // 調(diào)用onInterceptTouchEvent(ev)方法侮措,并將返回值賦給intercepted
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                 //當(dāng)禁止攔截時(shí)懈叹,指定intercepted = false乖杠,表示不攔截事件
                intercepted = false;
            }
        } else {
            //當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(沒(méi)有控件消費(fèi)touch事件)時(shí)
            //設(shè)置 intercepted = true,表示ViewGroup執(zhí)行Touch事件攔截的操作澄成。
            intercepted = true;
        }
        ...
         
        // 事件分發(fā)
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        //不是ACTION_CANCEL事件并且intercepted為false(ViewGroup不攔截事件onInterceptTouchEvent()方法返回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(); 
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS;

                removePointersFromTouchTargets(idBitsToAssign);
                final int childrenCount = mChildrenCount;
                if (childrenCount != 0) {
                    // 依據(jù)Touch坐標(biāo)尋找孩子控件來(lái)消費(fèi)Touch事件
                    final View[] children = mChildren;
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);

                    final boolean customOrder = isChildrenDrawingOrderEnabled();
                    // 遍歷所有孩子控件胧洒,判斷哪個(gè)消費(fèi)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) {
                            // 找到消費(fèi)Touch事件的孩子控件畏吓,跳出循環(huán),并用newTouchTarget表示孩子控件
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        resetCancelNextUpFlag(child);
                        // 沒(méi)有跳出循環(huán)卫漫,走到這一步菲饼,就會(huì)調(diào)用dispatchTransformedTouchEvent()方法,將事件傳給孩子控件做遞歸處理列赎,第三個(gè)參數(shù)不為null
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                           ...
                        }
                    }
                }
                 
                /**
                 * 如果在循環(huán)中沒(méi)有孩子控件消費(fèi)事件并且之前的mFirstTouchTarget不為空
                 */
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 將mFirstTouchTarget的賦給newTouchTarget
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    // newTouchTarget指向了最初的TouchTarget
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }
         
        /**
         * 分發(fā)Touch事件至目標(biāo)控件(target)宏悦,以上過(guò)程主要針對(duì)ACTION_DOWN,
         * 如果不是(上一步中判斷intercepted變量)包吝,比如ACTION_MOVE和ACTION_UP饼煞,就是從此處開(kāi)始執(zhí)行
         */
        if (mFirstTouchTarget == null) {
            /**
             * mFirstTouchTarget為null表示Touch事件未被消費(fèi)或Touch事件被攔截了,
             * 則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法诗越,遞歸處理砖瞧,第三個(gè)參數(shù)為null
             */
            handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
        } else {
            /**
             * mFirstTouchTarget不為null表示找到了可以消費(fèi)Touch事件的子View
             * 并且MOVE或UP事件可以傳遞到該子View
             */
            TouchTarget predecessor = null;
            // 將找到的可以消費(fèi)事件的mFirstTouchTarget賦給目標(biāo)控件(target)
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                // 如果已經(jīng)分發(fā)到新的控件并且消費(fèi)事件的目標(biāo)控件就是新的控件
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                    // 否則調(diào)用dispatchTransformedTouchEvent()方法進(jìn)行遞歸處理,第三個(gè)參數(shù)不為null
                    if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                   ...
                }
                predecessor = target;
                target = next;
            }
        }

        /**
         * 如果是ACTION_UP和ACTION_CANCEL事件嚷狞,還原狀態(tài)
         */
        if (canceled|| actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            ...
        }
    }
    ...
    return handled;
}

我們可以看到在上面的方法中块促,調(diào)用的onInterceptTouchEvent()判斷是否需要攔截事件。

查看ViewGroup中的dispatchTransformedTouchEvent()方法:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    final boolean handled;
    ...
        if (child == null) {
            // 如果孩子控件為空床未,就調(diào)用View的dispatchTouchEvent()方法竭翠,在View的dispatchTouchEvent()方法中會(huì)調(diào)用onTouchEvent()方法
            handled = super.dispatchTouchEvent(event);
        } else {
            // 如果孩子控件不為空,就調(diào)用孩子控件的dispatchTouchEvent()方法
            // 在此處孩子控件也還有可能是ViewGroup即硼,所以就是繼續(xù)調(diào)用ViewGroup的dispatchTouchEvent()方法
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

   ...
    transformedEvent.recycle();
    return handled;
}

查看ViewGroup中onInterceptTouchEvent()方法:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

在ViewGroup中逃片,沒(méi)有重寫onTouchEvent()方法,所以調(diào)用的是View中的onTouchEvent()方法只酥。

在View類中褥实,首先看一下View中的dispatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    // 如果是DOWN事件,重置狀態(tài)
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }

    // 過(guò)濾觸摸安全策略裂允,如果是false(窗口或控件被遮住了時(shí))损离,直接跳出觸摸事件
    // 如果應(yīng)該分發(fā)事件(調(diào)用onTouch()或onTouchEvdent()方法),則返回True;如果應(yīng)該刪除事件绝编,則返回false
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            // 當(dāng)前控件是可用(enabled)的并且View調(diào)用了setOnTouchListener()方法且返回了true僻澎,那么就設(shè)置result為true
            result = true;
        }
        
        // result為false,表示沒(méi)有調(diào)用setOnTouchListener()方法或該方法返回false十饥,那么就調(diào)用
        // View的onTouchEvent()方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

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

    // 如果是UP事件或CANCEL事件或者是DOWN事件但是該控件不能消費(fèi)事件時(shí)窟勃,重置狀態(tài)
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
    return result;
}

最后查看View的onTouchEvent()方法:

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

    // 判斷是否有單擊或長(zhǎng)按事件
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // 控件disabled 了,他還能消耗觸摸事件逗堵,只是不相應(yīng)她了
        return clickable;
    }
    // 如果有代理秉氧,調(diào)用代理的方法
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    // 對(duì)點(diǎn)擊事件的具體處理,只要有點(diǎn)擊事件蜒秤,那么onTouchEvent()方法就返回了 true
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // ...

                    // mHasPerformedLongPress 表示長(zhǎng)按事件的返回值汁咏,如果長(zhǎng)按事件的的回調(diào)方法返回了true亚斋,那么在同一事件序列中,點(diǎn)擊事件就不會(huì)調(diào)用了(否則會(huì)同時(shí)相應(yīng)長(zhǎng)按事件和點(diǎn)擊事件)
                    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(); 
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal(); // 會(huì)調(diào)用 performClick()方法處理單擊事件
                            }
                        }
                    }

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

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                mHasPerformedLongPress = false;

                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();

                // 根據(jù)是否在滾動(dòng)容器中攘滩,使用不同方式調(diào)用長(zhǎng)按事件的回調(diào)
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap(); // 最終調(diào)用長(zhǎng)按事件回調(diào)
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    checkForLongClick(0, x, y); // 最終調(diào)用長(zhǎng)按事件回調(diào)
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                // ...
                break;
        }

        // 只要有點(diǎn)擊事件帅刊,那么onTouchEvent()方法就返回了 true
        return true;
    }

    return false;
}

從上面的代碼來(lái)看,只要View的 CLICLABLE和LONG_CLICKABLE有一個(gè)為true漂问,那么onTouchEvent()方法就會(huì)返回true赖瞒,而且不管該View是否為DISABLE狀態(tài),其他情況返回false蚤假。true表示該控件可以消費(fèi)事件冒黑,false表示該控件不能消費(fèi)事件。現(xiàn)在勤哗,在回過(guò)頭來(lái)看一下第一張圖抡爹,是不是更加的清晰了呢。

最后芒划,對(duì)于View 的 CLICKABLE 和 LONG_CLICKABLE默認(rèn)值冬竟,LONG_CLICKABLE默認(rèn)值為false,但是對(duì)于CLICKABLE就要根據(jù)具體的View來(lái)看了民逼,確切的說(shuō)是可點(diǎn)擊的View的CLICKABLE值為true泵殴,如Button,不可點(diǎn)擊的View的CLICKABLE值為false拼苍,比如TextView笑诅,但是當(dāng)我們調(diào)用了View的 setOnClickListener(@Nullable OnClickListener l) 方法或者 setOnLongClickListener(@Nullable OnLongClickListener l) 方法就會(huì)將對(duì)應(yīng)的值改為true。

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

public void setOnLongClickListener(@Nullable OnLongClickListener l) {
    if (!isLongClickable()) {
        setLongClickable(true);
    }
    getListenerInfo().mOnLongClickListener = l;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疮鲫,一起剝皮案震驚了整個(gè)濱河市吆你,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俊犯,老刑警劉巖妇多,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異燕侠,居然都是意外死亡者祖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門绢彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)七问,“玉大人,你說(shuō)我怎么就攤上這事茫舶⌒笛玻” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)坟比。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嚷往,這世上最難降的妖魔是什么葛账? 我笑而不...
    開(kāi)封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮皮仁,結(jié)果婚禮上籍琳,老公的妹妹穿的比我還像新娘。我一直安慰自己贷祈,他們只是感情好趋急,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著势誊,像睡著了一般呜达。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粟耻,一...
    開(kāi)封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天查近,我揣著相機(jī)與錄音,去河邊找鬼挤忙。 笑死霜威,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的册烈。 我是一名探鬼主播戈泼,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赏僧!你這毒婦竟也來(lái)了大猛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤淀零,失蹤者是張志新(化名)和其女友劉穎胎署,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體窑滞,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琼牧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哀卫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巨坊。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖此改,靈堂內(nèi)的尸體忽然破棺而出趾撵,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布占调,位于F島的核電站暂题,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏究珊。R本人自食惡果不足惜薪者,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剿涮。 院中可真熱鬧言津,春花似錦、人聲如沸取试。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞬浓。三九已至初婆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猿棉,已是汗流浹背烟逊。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铺根,地道東北人宪躯。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像位迂,于是被迫代替她去往敵國(guó)和親访雪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容