Android View的事件分發(fā)

View的事件分發(fā)所針對(duì)的是MotionEvent事件叼丑,在Touch過(guò)程中會(huì)產(chǎn)生大量的MotionEvent欣范,記錄了與Touch相關(guān)的事件掠河。一次ACTION_DOWN栈虚、中間可能多次ACTION_MOVE、一次ACTION_UP汁掠,這便是一次完整的MotionEvent事件略吨。在我們點(diǎn)擊屏幕的那一刻,會(huì)先經(jīng)過(guò)硬件的一系列處理考阱,然后在當(dāng)前應(yīng)用的主(UI)線程中接收到來(lái)自底層傳輸過(guò)來(lái)的input事件翠忠,將事件交付于ViewRootImpl的enqueueInputEvent()方法,通過(guò)ViewRootImpl的內(nèi)部類InputStage轉(zhuǎn)換處理乞榨,最終交給View的dispatchTouchEvent()方法秽之,事件分發(fā)的開(kāi)始。
一個(gè)應(yīng)用程序的根視圖(頂級(jí)View)是DecorView吃既,也就是說(shuō)考榨,View的事件分發(fā)實(shí)際是由DecorView中的dispatchTouchEvent()方法開(kāi)始的。

在處理View的事件分發(fā)時(shí),View和ViewGroup(繼承自View)稍有差異。

  • View的相關(guān)處理方法:
    dispatchTouchEvent()糯钙、onTouchEvent()
  • ViewGroup的相關(guān)處理方法:
    dispatchTouchEvent()荔泳、onInterceptTouchEvent()、onTouchEvent()

1护奈、DecorView # dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

上面代碼中单鹿,mWindow是一個(gè)PhoneWindow類型的變量园匹,在Activity的attach()方法中對(duì)mWindow進(jìn)行賦值【可以參考這篇文章 — Activity的啟動(dòng)過(guò)程】乐尊。且Activity實(shí)現(xiàn)了Window.Callback接口(實(shí)現(xiàn)Window.Callback的不只有Activity戚丸,比如Dialog,這里以Activity為例)扔嵌,也就是說(shuō):cb.dispatchTouchEvent()回調(diào)的是Activity中的dispatchTouchEvent()昏滴,執(zhí)行這一步首先要滿足前提條cb是否存在,PhoneWindow沒(méi)有銷(xiāo)毀对人,mFeatureId < 0表示DecorView存在。否則會(huì)執(zhí)行ViewGroup的dispatchTouchEvent()方法(DecorView繼承FrameLayout)拂共。

2牺弄、Activity # dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //內(nèi)部是一個(gè)空實(shí)現(xiàn),用于點(diǎn)擊時(shí)與用戶交互
            onUserInteraction();
        }
        //getWindow()返回PhoneWindow對(duì)象
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //若前面沒(méi)有消費(fèi)事件宜狐,最后交給Activity的onTouchEvent
        return onTouchEvent(ev);
    }

這里的實(shí)現(xiàn)很簡(jiǎn)潔势告,先交給PhoneWindow分發(fā),如果沒(méi)有消費(fèi)事件抚恒,則在Activity的onTouchEvent()方法中消費(fèi)咱台。

3、PhoneWindow # superDispatchTouchEvent()

 public boolean superDispatchTouchEvent(MotionEvent event) {
        //mDecor指的是DecorView
        return mDecor.superDispatchTouchEvent(event);
    }

進(jìn)入DecorView中的superDispatchTouchEvent()

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

前面已注明了DecorView是繼承自FrameLayout(繼承ViewGroup)俭驮,那么這里也就自然而然的把分發(fā)任務(wù)交給了ViewGroup的dispatchTouchEvent()回溺。

4、ViewGroup # dispatchTouchEvent()混萝,由于代碼量太長(zhǎng)遗遵,這里選擇關(guān)鍵性的代碼

public boolean dispatchTouchEvent(MotionEvent ev) {
    ... ...

        // 如果是點(diǎn)擊事件,先對(duì)之前的狀態(tài)進(jìn)行清除
        //1逸嘀、把mFirstTouchTarget(TouchTarget對(duì)象)鏈表清空车要,同時(shí)mFirstTouchTarget置空
        //mFirstTouchTarget鏈表內(nèi)部存放的是接受了觸摸事件的view
        //2、重置FLAG_DISALLOW_INTERCEPT標(biāo)識(shí)位(若設(shè)置了這個(gè)標(biāo)識(shí)崭倘,表示禁止ViewGroup的攔截)
        //可以通過(guò)requestDisallowInterceptTouchEvent()進(jìn)行設(shè)置翼岁,
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 檢查是否進(jìn)行攔截
        final boolean intercepted;

        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {

            // 檢查是否已設(shè)置FLAG_DISALLOW_INTERCEPT標(biāo)識(shí)
            // 若設(shè)置,則不進(jìn)行攔截司光,intercepted為false
            // 否則攔截琅坡,由onInterceptTouchEvent()決定intercepted的狀態(tài)
            // onInterceptTouchEvent()默認(rèn)返回false
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            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.
            intercepted = true;
        }

    ... ...

        // 檢查當(dāng)前觸摸事件是否被取消
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        //把當(dāng)前ViewGrop的觸摸事件進(jìn)行分發(fā)給子View和子ViewGroup
        //在這之前需要滿足兩個(gè)條件:1、觸摸事件沒(méi)有取消  2飘庄、沒(méi)有被攔截
        //如果當(dāng)前ViewGroup的子View接收到觸摸事件脑蠕,則把該子View添加到mFirstTouchTarget鏈表
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {

            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                //記錄觸摸事件的序列號(hào),事件Id,點(diǎn)擊事件的Id總是為0谴仙,
                //在多指觸控下迂求,會(huì)產(chǎn)生多個(gè)Id,比如第一根手指,記錄0晃跺,第二根手機(jī)記錄1 ~ ~ ~
                final int actionIndex = ev.getActionIndex();
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // 清除早期的觸摸目標(biāo)                 
                removePointersFromTouchTargets(idBitsToAssign);

                // 獲取當(dāng)前ViewGroup包含的子元素揩局,進(jìn)行遍歷,然后對(duì)觸摸事件進(jìn)行分發(fā)
                // 如果子元素也是ViewGroup掀虎,那么就對(duì)其里面的子元素遍歷凌盯,如此遞歸下去
                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;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        //判斷子View是否能接收觸摸事件
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        // 先查找mFirstTouchTarget鏈表,是否存在該View
                        // 若查找到烹玉,則返回當(dāng)前View在鏈表中的節(jié)點(diǎn)賦值給newTouchTarget
                        // 若沒(méi)有驰怎,則返回null
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        //重置子View的mPrivateFlags中的PFLAG_CANCEL_NEXT_UP_EVENT
                        resetCancelNextUpFlag(child);

                        //將觸摸事件分發(fā)給子View
                        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;
                            }

                            //若子元素(包含子View和子ViewGroup)可以接收到觸摸事件,
                            // 通過(guò)addTouchTarget()把已接收觸摸事件的子元素添加到
                            //mFirstTouchTarget鏈表二打,并把當(dāng)前子元素作為頭結(jié)點(diǎn)返回县忌,賦值給newTouchTarget
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            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();
                }

                //在newTouchTarget為null,mFirstTouchTarget不為null時(shí)继效,
                //把mFirstTouchTarget賦值給newTouchTarget症杏,作為鏈表第一個(gè)節(jié)點(diǎn)
                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;
                }
            }
        }

... ...

        // 進(jìn)一步對(duì)觸摸事件的分發(fā)進(jìn)行處理,mFirstTouchTarget==null表示未有
        // 子View接收到觸摸事件瑞信,此時(shí)厉颤,會(huì)交由ViewGroup的父類View的dispatchTouchEvent()進(jìn)行分發(fā),
        //然后再交給onTouch()或onTouchEvent()進(jìn)行處理
        //onTouch()優(yōu)先于onTouchEvent()凡简,但是要setOnTouchListener()后才生效
        // 如果mFirstTouchTarget != null逼友,表示存在子View,則分發(fā)到子View
        if (mFirstTouchTarget == null) {        
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } 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) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    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;
            }
        }

        // 完成后重置觸摸狀態(tài)
        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;
 }

關(guān)于ViewGroup的事件分發(fā)秤涩,由于代碼太長(zhǎng)翁逞,所以直接在里面注釋了,這樣跟著代碼會(huì)比較容易理解溉仑。上面闡述了關(guān)于ViewGroup的整個(gè)分發(fā)過(guò)程挖函,在第二個(gè)省略號(hào)的下面一段代碼,從其 if 條件可以知道浊竟,這段代碼只有在ViewGroup發(fā)生ACTION_DOWN事件時(shí)才會(huì)執(zhí)行(分發(fā)事件)怨喘,而后續(xù)的事件(ACTION_MOVE、ACTION_UP)將由第三個(gè)省略號(hào)下面一段代碼中執(zhí)行分發(fā)振定,這時(shí)會(huì)遍歷mFirstTouchTarget鏈表必怜,找到具體的子View進(jìn)行分配事件。(實(shí)際就是:假設(shè)當(dāng)前ViewGroup中包含三個(gè)子View后频,分別是A B C梳庆,如果ACTION_DOWN事件分發(fā)到了A暖途,那么后續(xù)的事件一定不會(huì)分發(fā)到B或C)
下面進(jìn)一步去分析dispatchTransformedTouchEvent()

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        //這里檢測(cè)是否需要發(fā)送ACTION_CANCEL事件。
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {    
                //分發(fā)給當(dāng)前ViewGroup
                handled = super.dispatchTouchEvent(event);
            } else {   
               //分發(fā)給子View
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // 計(jì)算觸摸事件Id
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        if (newPointerIdBits == 0) {
            return false;
        }
      
        final MotionEvent transformedEvent;
        //若觸摸事件Id相同膏执,不需要重新計(jì)算MotionEvent驻售,直接進(jìn)行分發(fā)
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                //child為null,交由父類分發(fā)更米,否則交由child分發(fā)
                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;
    }

在dispatchTransformedTouchEvent()中主要是對(duì)子View進(jìn)行判斷欺栗,如果子View為null,則分發(fā)交由super.dispatchTouchEvent()征峦,也就是由View的dispatchTouchEvent()分發(fā)迟几,然后交給onTouch()(如果設(shè)置了OnTouchListener)或onTouchEvent(),如果這時(shí)候onTouchEvent()依舊返回false栏笆,則交由當(dāng)前ViewGroup的上一級(jí)去處理类腮。

5、View # dispatchTouchEvent()蛉加,省略了部分代碼

public boolean dispatchTouchEvent(MotionEvent event) {
       ... ...

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 接收到ACTION_DOWN存哲,停止嵌套滑動(dòng)
            stopNestedScroll();
        }
        
         //判斷View是否被屏蔽,是否能被點(diǎn)擊七婴,
        //然后再確定是否執(zhí)行onTouch或onTouchEvent
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }

            //在這里注意下li.mOnTouchListener.onTouch(),這里說(shuō)明了
            //onTouch會(huì)先于onTouchEvent執(zhí)行察滑,前提:當(dāng)前View設(shè)置了OnTouchListener
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            
            //若沒(méi)有設(shè)置OnTouchListener打厘,交給onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

    ... ...

        return result;
    }

關(guān)鍵的步驟已在代碼中注釋,就不再另外贅述了贺辰。
接著看下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();
        
        //計(jì)算View是否可點(diǎn)擊
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        
        //這里View被禁用,調(diào)用了setEnabled(false)或android:enabled=false
        //返回其點(diǎn)擊狀態(tài)饲化,View默認(rèn)是不可點(diǎn)擊的
        //可以通過(guò)setClickable()或者android:clickable設(shè)置點(diǎn)擊狀態(tài)
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;        
            return clickable;
        }

        //委托事件給其它View莽鸭,mTouchDelegate默認(rèn)為null
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        //判斷View的點(diǎn)擊狀態(tài),設(shè)置焦點(diǎn)吃靠。后續(xù)執(zhí)行onClick()硫眨、onLongClick()
        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) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        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)) {
                                    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();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        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);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

關(guān)于View的事件分發(fā)大概流程到這里就結(jié)束了。
Activity > ViewGroup(可包含多個(gè)ViewGroup) > View 巢块。
最后通過(guò)一張圖的形式整理下上面的思路:


View的事件分發(fā).png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末礁阁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子族奢,更是在濱河造成了極大的恐慌姥闭,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件越走,死亡現(xiàn)場(chǎng)離奇詭異棚品,居然都是意外死亡靠欢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)铜跑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)门怪,“玉大人,你說(shuō)我怎么就攤上這事疼进⌒嚼拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵伞广,是天一觀的道長(zhǎng)拣帽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嚼锄,這世上最難降的妖魔是什么减拭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮区丑,結(jié)果婚禮上拧粪,老公的妹妹穿的比我還像新娘。我一直安慰自己沧侥,他們只是感情好可霎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著宴杀,像睡著了一般癣朗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旺罢,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天旷余,我揣著相機(jī)與錄音,去河邊找鬼扁达。 笑死正卧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跪解。 我是一名探鬼主播炉旷,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叉讥!你這毒婦竟也來(lái)了砾跃?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤节吮,失蹤者是張志新(化名)和其女友劉穎抽高,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體透绩,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翘骂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年壁熄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碳竟。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡草丧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出莹桅,到底是詐尸還是另有隱情昌执,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布诈泼,位于F島的核電站懂拾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏铐达。R本人自食惡果不足惜岖赋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓮孙。 院中可真熱鬧唐断,春花似錦、人聲如沸杭抠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)偏灿。三九已至丹诀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菩混,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工扁藕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沮峡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓亿柑,卻偏偏與公主長(zhǎng)得像邢疙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子望薄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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