Android - View 事件分发机制

1、事件调用顺序

以Button为例,以下分别为 onClick、onTouch 

button.setOnClickListener(new OnClickListener() {  
    @Override  
    public void onClick(View v) {  
        Log.d("TAG", "onClick execute");  
    }  
}); 
button.setOnTouchListener(new OnTouchListener() {  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        Log.d("TAG", "onTouch execute, action " + event.getAction());  
        return false;  
    }  
});  

  调用顺序:onTouch->onClick, 并且OnTouch调用两次(ACTION_DOWN、ACTION_UP)

  onTouch有返回值,true表示事件被onTouch消费掉了,不会继续向下传递;

2、控件的dispatchTouchEvent方法

  Button控件的dispatchTouchEvent继承于View (Button->TextView->View)

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

  如果onTouchListener监听器不为空, 当前点击的控件是enable的,mOnTouchListener.onTouch(this, event)返回TRUE,就返回TRUE,否则就去执行onTouchEvent(event)方法并返回;

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

  首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。

  在经过种种判断之后,会执行到第38行的performClick()方法: mOnClickListener.onClick(this) 就是在此时被调用;

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}  

  3、Touch事件的层级传递

  我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。

  这里需要注意,如果处理ACTION_DOWN时,dispatchTouchEvent返回FALSE,后面一系列其它的action就不会再得到处理。总之,当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

    参考前面的源码,首先在OnTouchListener之onTouch事件里返回了false,代码执行进入到控件onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。

  由于我们点击了按钮,代码执行进入到第14行的IF分支,不管action类型,最终都会返回一个true。如果将Button换成ImageView,因为ImageView默认不可点击,onTouchEvent中代码执行进入else分支,dispatchTouchEvent返回false;

  4、总结

    OnTouchListener之OnTouch事件,返回False,代码执行进入控件OnTouchEvent方法;

    控件OnTouchEvent方法中,返回True,才会继续触发后面的action(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

    比如点击事件,先响应ACTION_DOWN,返回true,然后手抬起,又从dispatchTouchEvent()分发,再响应ACTION_UP

  

 参考链接:简书 http://www.jianshu.com/p/b1ee0985da16

      郭神 http://blog.csdn.net/guolin_blog/article/details/9097463  

posted @ 2016-02-26 23:47  chenyizh  阅读(146)  评论(0)    收藏  举报