Android中的view全解析(四)

最後,我們來看一下View的事件分發機制。

當我們對一個View進行點擊時(Button也好,ImageView也好),首先會調用View的dispatchTouchEvent方法,方法的代碼如下:
    public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }  
先看if中的語句,
1. mOnTouchListener != null View被設置了OnTouchListener
2. (mViewFlags & ENABLED_MASK) == ENABLED View的狀態是enabled,enabled的狀態表明View可以接收事件
3. mOnTouchListener.onTouch(this, event) onTouch方法返回值爲true
當以上3個條件都成立時,返回值爲true。否則,就執行onTouchEvent方法,返回該方法的返回值。

下面是onTouchEvent方法的具體代碼:

   
public boolean onTouchEvent(MotionEvent event) {  
        final int viewFlags = mViewFlags;  
        if ((viewFlags & ENABLED_MASK) == DISABLED) {  
            // 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 & PREPRESSED) != 0;  
                    if ((mPrivateFlags & 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 (!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) {  
                            mPrivateFlags |= PRESSED;  
                            refreshDrawableState();  
                            postDelayed(mUnsetPressedState,  
                                    ViewConfiguration.getPressedStateDuration());  
                        } else if (!post(mUnsetPressedState)) {  
                            // If the post failed, unpress right now  
                            mUnsetPressedState.run();  
                        }  
                        removeTapCallback();  
                    }  
                    break;  
                case MotionEvent.ACTION_DOWN:  
                    if (mPendingCheckForTap == null) {  
                        mPendingCheckForTap = new CheckForTap();  
                    }  
                    mPrivateFlags |= PREPRESSED;  
                    mHasPerformedLongPress = false;  
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                    break;  
                case MotionEvent.ACTION_CANCEL:  
                    mPrivateFlags &= ~PRESSED;  
                    refreshDrawableState();  
                    removeTapCallback();  
                    break;  
                case MotionEvent.ACTION_MOVE:  
                    final int x = (int) event.getX();  
                    final int y = (int) event.getY();  
                    // Be lenient about moving outside of buttons  
                    int slop = mTouchSlop;  
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                            (y < 0 - slop) || (y >= getHeight() + slop)) {  
                        // Outside button  
                        removeTapCallback();  
                        if ((mPrivateFlags & PRESSED) != 0) {  
                            // Remove any future long press/tap checks  
                            removeLongPressCallback();  
                            // Need to switch from pressed to not pressed  
                            mPrivateFlags &= ~PRESSED;  
                            refreshDrawableState();  
                        }  
                    }  
                    break;  
            }  
            return true;  
        }  
        return false;  
    }  


在該方法中,首先判斷View是否是可點擊的,或者可長點擊的,如果是,進入switch判斷,並在MotionEvent_UP事件中調用了performClick方法,在performClick方法中判斷,如果View設置了OnClickListener,就會回調執行了onClick方法。
但要注意一點,如果View是可點擊的或者是可長點擊的,一旦進入了if的語句段,最終的返回值爲true。這是什麼意思呢?如果我們在onTouch中的返回值爲false,那麼只要這個View是可點擊的,最終的返回值也爲true。
這裏就要區分一下,onTouch返回值中的含義了。如果onTouch返回爲true,表明View的onTouch方法將此事件消費掉了,不再向下傳遞,並且將繼續監聽View的其他事件。由於onTouch方法將事件消費掉了,那麼onClick方法和onLongClick方法都無法接受到該事件了。如果onTouch方法返回爲false,表明onTouch方法沒有消費該事件,事件將會向下繼續傳遞給onClick和onLongClick方法,事件將會被onClick或者onLongClick方法消費,於是返回true,表明View的onClick和onClick方法還將繼續接收事件,而由於onTouch方法接收事件在onClick和onLongClick之前,所以它也會再繼續接受事件。
總結一下,這個最終的返回值爲true或者false並不是由onTouch方法決定的,而是由事件是否被View所消費決定的。如果View是可點擊的,那麼它總能消費事件,返回值也始終爲true。每個ENABLED狀態的View都可接收事件,默認均可被觸摸,所以要在onTouch方法中返回值來判斷是否要消費該事件。此時要注意區分接收事件和消費事件的區別。

上面分析了View的事件分發機制,下面來看一看ViewGroup中的事件分發。
首先,ViewGroup中也有dispatchTouchEvent方法,我們來看一看它的具體實現。

   
public boolean dispatchTouchEvent(MotionEvent ev) {  
        final int action = ev.getAction();  
        final float xf = ev.getX();  
        final float yf = ev.getY();  
        final float scrolledXFloat = xf + mScrollX;  
        final float scrolledYFloat = yf + mScrollY;  
        final Rect frame = mTempRect;  
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
        if (action == MotionEvent.ACTION_DOWN) {  
            if (mMotionTarget != null) {  
                mMotionTarget = null;  
            }  
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
                ev.setAction(MotionEvent.ACTION_DOWN);  
                final int scrolledXInt = (int) scrolledXFloat;  
                final int scrolledYInt = (int) scrolledYFloat;  
                final View[] children = mChildren;  
                final int count = mChildrenCount;  
                for (int i = count - 1; i >= 0; i--) {  
                    final View child = children[i];  
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                            || child.getAnimation() != null) {  
                        child.getHitRect(frame);  
                        if (frame.contains(scrolledXInt, scrolledYInt)) {  
                            final float xc = scrolledXFloat - child.mLeft;  
                            final float yc = scrolledYFloat - child.mTop;  
                            ev.setLocation(xc, yc);  
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                            if (child.dispatchTouchEvent(ev))  {  
                                mMotionTarget = child;  
                                return true;  
                            }  
                        }  
                    }  
                }  
            }  
        }  
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                (action == MotionEvent.ACTION_CANCEL);  
        if (isUpOrCancel) {  
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
        }  
        final View target = mMotionTarget;  
        if (target == null) {  
            ev.setLocation(xf, yf);  
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
                ev.setAction(MotionEvent.ACTION_CANCEL);  
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            }  
            return super.dispatchTouchEvent(ev);  
        }  
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
            final float xc = scrolledXFloat - (float) target.mLeft;  
            final float yc = scrolledYFloat - (float) target.mTop;  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            ev.setLocation(xc, yc);  
            if (!target.dispatchTouchEvent(ev)) {  
            }  
            mMotionTarget = null;  
            return true;  
        }  
        if (isUpOrCancel) {  
            mMotionTarget = null;  
        }  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        ev.setLocation(xc, yc);  
        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            mMotionTarget = null;  
        }  
        return target.dispatchTouchEvent(ev);  
    }  


注意到,代碼中有這麼一個判斷句:
if (disallowIntercept || !onInterceptTouchEvent(ev))
if中有兩個條件,只要滿足其一,就會進入下面的代碼段。
1. disallowIntercept ViewGroup是否禁用掉事件攔截功能,true表示禁用,false表示不禁用。
2. !onInterceptTouchEvent(ev) ViewGroup是否攔截該事件。該條件對onInterceptTouchEvent方法的返回值進行了取反操作,如果onInterceptTouchEvent方法的返回值爲false,即不攔截,那麼該條件成立。
總結一下,如果ViewGroup禁用掉了事件攔截功能,或者ViewGroup沒有對該事件進行攔截,那麼將進入下面的代碼段。下面的代碼段是在做什麼呢?

for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                        if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
看這個for循環應該就很明白了,它獲取了ViewGroup中可以接收該事件的childView,將事件向下傳遞了。childView接收到了該事件後,該怎麼辦呢?下面是for循環中的if語句。
if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  

有if中的條件可見,如果childView將事件消費掉了,那麼ViewGroup的dispatchTouchEvent方法直接返回true,ViewGroup不再處理該事件。否則,將繼續執行下面的代碼段,由ViewGroup來處理該事件。

總結:
1. Android中的事件傳遞是由ViewGroup開始在dispatchTouchEvent方法中向下傳遞,先傳遞到ViewGroup,再由ViewGroup決定是否傳遞到View。
2. 如果ViewGroup的onInterceptTouchEvent將事件攔截了,或者childView沒有將事件消費掉,ViewGroup將處理該事件。
3. 如果childView處理了該事件,那麼ViewGroup便不會處理該事件。




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