Android - View 事件分发机制

1、事件调用顺序

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

1
2
3
4
5
6
button.setOnClickListener(new OnClickListener() { 
    @Override 
    public void onClick(View v) { 
        Log.d("TAG", "onClick execute"); 
    
});
1
2
3
4
5
6
7
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)

1
2
3
4
5
6
7
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)方法并返回;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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) 就是在此时被调用;

1
2
3
4
5
6
7
8
9
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 @   chenyizh  阅读(133)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示