安卓中的事件分發機制源碼解析

安卓中的事件分發機制主要涉及到兩類控件,一類是容器類控件ViewGroup,如常用的佈局控件,另一類是顯示類控件,即該控件中不能用來容納其它控件,它只能用來顯示一些資源內容,如Button,ImageView等控件。暫且稱前一類控件爲ViewGroup類控件(儘管ViewGroup本身也是一個View),後者爲View類控件。


安卓中的事件分發機制主要涉及到dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)這三個方法,下面先簡單的介紹一下各自的作用。

public boolean dispatchTouchEvent(MotionEvent ev)

用來進行事件的分發,如果事件能夠傳遞給當前View則此方法一定會調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent的返回值的影響,表示是否消耗當前事件。

public boolean onInterceptTouchEvent(MotionEvent ev)

在dispatchTouchEvent函數的內部被調用,用來判斷是否攔截某個事件,注意View類的控件不包含此方法,即一旦點擊事件傳遞給View,那麼View的onTouchEvent方法將會被調用,而ViewGroup默認是不攔截任何事件,即Android源碼中ViewGroup的onInterceptTouchEvent(MotionEvent ev)方法默認返回false,這一點稍後再源碼解析中可以看到。

public boolean onTouchEvent(MotionEvent ev)

在dispatchTouchEvent函數的內部被調用,用來處理點擊事件,返回結果表示是否消耗當前事件

這三者之間的關係可以用如下結論來描述:

對於一個ViewGroup而言,點擊事件產生後首先會傳遞給它,它的dispatchTouchEvent函數將會被調用,如果該ViewGroup的onInterceptTouchEvent返回true,則表示它要攔截當前事件,那麼事件就會交給該ViewGroup處理,即它的 onTouchEvent方法會被調用,如果該ViewGroup的onInterceptTouchEvent返回false則表示它不攔截當前事件(默認返回false),這時事件就會傳遞給它的子控件,即子控件的dispatchTouchEvent函數將會被調用,整個事件分發過程重複上述過程,直至事件被處理。

當事件被傳遞到View控件時,則它一定會處理該事件,View控件不存在onInterceptTouchEvent方法,此時如果該View設置了OnTouchListener,那麼OnTouchListener中的onTouch方法將會被調用,如果onTouch方法返回false,那麼該View的onTouchEvent方法將會被調用,否則onTouchEvent方法不會被調用,在onTouchEvent方法中,如果該Vie設置了OnClickListener,那麼它的onClick方法將會被調用。


安卓中的事件傳遞過程遵循如下順序:Activity->Window->View,即當點擊事件產生後,首先傳遞給Activity,Activity在傳遞給Window,最後Window傳遞給根View,根View接收到點擊事件後,將會按照前面講述的事件分發機制去分發事件。直至事件傳遞給非容器類View,如果一個非容器類的View的onTouchEvent方法返回false,那麼它的父容器的onTouchEvent方法將會被調用,以此類推,如果所有的View都不處理該事件,那麼這個事件最終會交給Activity處理。


下面我們從源碼的角度去驗證上述結論與過程。

一Activity對點擊事件的分發過程

點擊事件用MotionEvent來表示,我們知道一個點擊事件首先被Activity捕獲到,隨後Activity的dispatchTouchEvent將會被調用,我們來看一下Activity的dispatchTouchEvent的源碼:

 /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     * 
     * @param ev The touch screen event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
可以看到當我們點擊手機屏幕時,即產生MotionEvent.ACTION_DOWN動作時,會調用onUserInteraction()方法,該方法是一個空的方法,即不產生任何效果,然後會執行getWindow().superDispatchTouchEvent(ev),而getWindow()方法將會得到一個Window對象,然後會調用Window的superDispatchTouchEvent(ev)方法,這也就驗證了前面我們所說的Activity會將點擊事件傳遞給Window,如果getWindow().superDispatchTouchEvent(ev)返回true則Activity的dispatchTouchEvent將會直接返回true,如果返回false則意味着點擊事件在傳遞的過程中沒人處理,即所有View的onTouchEvent方法都返回了false,那麼Activity的onTouchEvent將會被調用。


至此Activity已經將事件傳遞給了Window,接下來我們來看一下,Window是如何將事件傳遞給ViewGroup的,Window是個抽象類,getWindow().superDispatchTouchEvent(ev)語句調用的事實上其子類的superDispatchTouchEvent(ev)方法,下面是其子類PhoneWindow的superDispatchTouchEvent(ev)方法源碼:

public boolean superDispatchTouchEvent(MotionEvent event)
{
     return mDecor.superDispatchTouchEvent(event);
}
可以看到在PhoneWindow的superDispatchTouchEvent方法中調用來mDecor的superDispatchTouchEvent方法,而mDecor本質上是一個DecorView,到這裏也就驗證了上面說的Window將會把點擊事件傳遞給DecorView。那麼我們接下來了解一下DecorView:

 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker 
DecorView被定義爲PhoneWindow的一個內部類,它是整個Window的dingjiView(This is the top-level view of the window),可以看到DecorView繼承自FrameLayout,即它本質上是一個FrameLayout,我們知道通常我們在Activity中獲取某個控件的id使用findViewById但事實上Activity中的findViewById內部調用的是Window中的findViewById方法,Activity中的findViewById源碼如下:

 public View findViewById(int id) {
        return getWindow().findViewById(id);
    }

而Window中的findViewById調用的是DecorView中的findViewById,代碼如下:

 public View findViewById(int id) {
        return getDecorView().findViewById(id);
    }
同樣的我們在Activity中使用的setContentView最終調用的也是DecorView中的setContentView,也就是說我們通過setContentView設置的佈局View是DecorView的一個子View,這也就驗證了上面說的DecorView會將點擊事件傳遞給根View,這個根View是我們通過setContentView設置的佈局文件的根View,一般來說根View都屬於ViewGroup類。

二根View對點擊事件的分發過程
這個是安卓事件分發機制中最重要的一部分。我們首先來看一下ViewGroup的dispatchTouchEvent方法,這個方法處理的邏輯很多,所以代碼比較複雜,如下:

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        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) {
                // 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.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                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;
            }

            // 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) {
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    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 View[] children = mChildren;

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        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) {
                                // 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;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }

                    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;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                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;
                }
            }

            // 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;
    }
代碼很長,我們先觀其大概,然後挑重點分析,首先我們來看一下如下的部分代碼:

 // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                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;
            }
從註釋上可以看到這部分代碼的功能是檢查是否攔截事件(Check for interception),可以看到ViewGroup在兩種情況下會判斷是否攔截當前事件,即事件類型爲MotionEvent.ACTION_DOWN(actionMasked == MotionEvent.ACTION_DOWN)或者mFirstTouchTarget != null,ACTION_DOWN這個很好理解,那麼mFirstTouchTarget != null是什麼意思呢?從後面的代碼可以看到當事件被ViewGroup的子控件成功處理時,mFirstTouchTarget會被賦值爲執行該子控件,即當ViewGroup不攔截事件而是將事件交給其子控件處理時mFirstTouchTarget != null,換而言之,如果事件被當前ViewGroup攔截則mFirstTouchTarget == null,那麼當.ACTION_DOWN與.ACTION_UP事件產生時,則因爲actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null不成立,那麼該if語句將不會被執行,即ViewGroup的onInterceptTouchEvent(ev)將不會被調用,即當某個ViewGroup決定攔截一個事件後,那麼這個事件序列都將由它來處理,而當其它事件序列產生時不再調用它的onInterceptTouchEvent(ev)去判斷是否攔截餘下事件序列。通俗的說就是,如果你的手指點擊按下(ACTION_DOWN)事件被該ViewGroup攔截,那麼其餘的如ACTION_MOVE,ACTION_UP都將被它處理。


上面我們分析了ViewGroup攔截事件的條件,那麼接下來我們看一下ViewGroup不攔截事件的條件,即將事件交給其子控件處理的過程,代碼如下:

 // Find a child that can receive the event.
                        // Scan children from front to back.
                        final View[] children = mChildren;

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        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) {
                                // 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;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    
從註釋上可以看到這段代碼的作用是找一個可以處理事件的子控件(Find a child that can receive the event.),其過程是遍歷ViewGroup中全部的子控件,然後判斷該子控件是否可以接受到點擊事件,這主要是通過兩個條件來判斷的:子控件是否在播放動畫,點擊事件的座標是否在該子控件的範圍內,第二個條件很好理解,在其範圍內則表示用戶點擊的是該控件,那麼點擊事件自然被它接收。如果某個子控件滿足上述兩個條件,那麼事件將會傳遞給它來處理。即調用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),在該方法中的第三個參數即爲上述遍歷過程中找到的滿足上述兩個條件的子控件,而在該方法的內部事實上就是直接調用了子控件的dispatchTouchEvent方法,這樣事件就交給了子控件去處理,代碼如下:

/**
     * 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.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
可以看到,如果child不爲空,則會調用子控件的dispatchTouchEvent(event)方法然後將其返回值賦給 handled最終返回。所以如果子元素的dispatchTouchEvent(event)方法返回true,那麼其父控件的dispatchTransformedTouchEvent也將會返回true,那麼包含if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))的if語句邏輯成立,將會執行該if語句,即如下代碼將會被執行:

 newTouchTarget = addTouchTarget(child, idBitsToAssign);
 alreadyDispatchedToNewTouchTarget = true;
 break;

即完成 newTouchTarget的賦值,然後跳出for循環,終止對子元素的遍歷(break),子元素的dispatchTouchEvent(event)方法返回false,那麼其父控件的dispatchTransformedTouchEvent也將會返回false,那麼該if語句不成立,所以ViewGroup將會把事件交給下一個子控件處理(如果下一個子控件存在的話)。

如果遍歷所有的子控件後事件都沒被處理,那麼ViewGroup將會自己處理點擊事件,這一版包括兩種情況:第一種ViewGroup不包含子控件,另一種正如上面所分析的子控件處理了點擊事件,但是在子控件dispatchTouchEvent(event)方法返回false,一般是子元素在onTouchEvent中返回了false。此時ViewGroup會自己處理點擊事件,代碼片段如下:

 if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
從註釋上可以看到,這段代碼表示此時無子控件響應點擊事件( No touch targets ),此時點擊事件會交給該ViewGroup自己處理,注意此時的dispatchTransformedTouchEvent函數的第三個參數傳入的是null,根據我們前面的分析此時,如下語句將會被執行:

 if (child == null) {
                handled = super.dispatchTouchEvent(event);}
即此時點擊事件傳遞給了View的dispatchTouchEvent方法,即點擊事件開始交給View來處理。



View對點擊事件的分發過程

View對點擊事件的處理過程相對而言就很簡單,(此處說的View不包括容器類GroupView),因爲View中不存在onInterceptTouchEvent方法,即如果點擊事件傳遞給了View那麼它一定會被該View處理。同樣的我們首先來看一下View的dispatchTouchEvent方法。代碼如下:

 public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }
可以看到View對點擊事件的處理過程首先會判斷是否設置過OnTouchListener(li != null && li.mOnTouchListener != null),View是否處於可以點擊狀態(mViewFlags & ENABLED_MASK) == ENABLED),View的OnTouchListener中的onTouch方法是否返回true( li.mOnTouchListener.onTouch(this, event)),這三個條件如果同時成立,那麼View的dispatchTouchEvent方法將直接返回true,即不會調用View的onTouchEvent(event),否則將會調用下面的if語句,即調用onTouchEvent(event)。我們來看一下View的onTouchEvent(event)方法,代碼如下:

 * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

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

        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) {
                        // 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);
                       }

                        if (!mHasPerformedLongPress) {
                            // 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();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    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();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true);
                        checkForLongClick(0);
                    }
                    break;

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

                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

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

                            setPressed(false);
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }

從第一個if判斷語句可以看到,即使當前View處於不可點擊狀態((viewFlags & ENABLED_MASK) == DISABLED),該狀態下的View照樣會消耗點擊事件(A disabled view that is clickable still consumes the touch events)。

我們重點看一下上述代碼中的第三個if語句,這個是onTouchEvent對點擊事件的具體處理,從上面的代碼可以看到只要View的CLICKABLE與LONG_CLICKABLE中某一個爲true,那麼該View就會消耗點擊事件。即onTouchEvent方法會返回true,而View的LONG_CLICKABLE屬性默認爲false,即通常View的onTouchEvent返回值很大程度上取決於CLICKABLE屬性,而CLICKABLE屬性與具體的View相關,具體來說如果該View爲可點擊的View則其CLICKABLE爲true,不可點擊的View其CLICKABLE屬性爲false,如Button是可點擊的,TextView是不可點擊的,通過setClickable和setLongClickable可以分別改變View的CLICKABLE和LONG_CLICKABLE屬性,另外,setOnClickListener會自動將View的CLICKABLE設置爲true,setOnLongClickListener會自動將View的LONG_CLICKABLE設置爲true。


另外可以看到當ACTION_UP事件發生時,會調用performClick方法,如果該View設置過OnClickListener,那麼在performClick方法的內部會調用onClick方法。


好了以上就是本人理解的關於安卓中事件分發機制,看官如果覺得不錯,請記得點個贊哦微笑











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章