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便不会处理该事件。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值