Android学习之事件分发机制

旧巷老猫 提交于 2019-12-18 11:14:51

博文出处:http://blog.csdn.net/sinyu890807/article/details/9097463

当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:

    button.setOnClickListener(new OnClickListener() {  
        @Override  
        public void onClick(View v) {  
            Log.d("TAG", "onClick execute");  
        }  
    });  

这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:

    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是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(还可能 会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。 onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,onclick方法就不再执行了。我们可以将这种情况看做触摸事件被onTouch方法拦截了,没有继续向onclick传递。 那么view的点击事件是怎么传递的呢?示意图:              
dispatchTouchEvent的源码如下:
    public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }  
 我们可以 看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:
 
    public void setOnTouchListener(OnTouchListener l) {  
        mOnTouchListener = l;  
    }  
 
Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
那么来分析一下: 首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的, 也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续 往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。
同时我们可以发现:onClick的调用肯定是在onTouchEvent(event)方法中的;onTouchEvent的源码
 1     public boolean onTouchEvent(MotionEvent event) {  
 2         final int viewFlags = mViewFlags;  
 3         if ((viewFlags & ENABLED_MASK) == DISABLED) {  
 4             // A disabled view that is clickable still consumes the touch  
 5             // events, it just doesn't respond to them.  
 6             return (((viewFlags & CLICKABLE) == CLICKABLE ||  
 7                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
 8         }  
 9         if (mTouchDelegate != null) {  
10             if (mTouchDelegate.onTouchEvent(event)) {  
11                 return true;  
12             }  
13         }  
14         if (((viewFlags & CLICKABLE) == CLICKABLE ||  
15                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
16             switch (event.getAction()) {  
17                 case MotionEvent.ACTION_UP:  
18                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
19                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
20                         // take focus if we don't have it already and we should in  
21                         // touch mode.  
22                         boolean focusTaken = false;  
23                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
24                             focusTaken = requestFocus();  
25                         }  
26                         if (!mHasPerformedLongPress) {  
27                             // This is a tap, so remove the longpress check  
28                             removeLongPressCallback();  
29                             // Only perform take click actions if we were in the pressed state  
30                             if (!focusTaken) {  
31                                 // Use a Runnable and post this rather than calling  
32                                 // performClick directly. This lets other visual state  
33                                 // of the view update before click actions start.  
34                                 if (mPerformClick == null) {  
35                                     mPerformClick = new PerformClick();  
36                                 }  
37                                 if (!post(mPerformClick)) {  
38                                     performClick();  
39                                 }  
40                             }  
41                         }  
42                         if (mUnsetPressedState == null) {  
43                             mUnsetPressedState = new UnsetPressedState();  
44                         }  
45                         if (prepressed) {  
46                             mPrivateFlags |= PRESSED;  
47                             refreshDrawableState();  
48                             postDelayed(mUnsetPressedState,  
49                                     ViewConfiguration.getPressedStateDuration());  
50                         } else if (!post(mUnsetPressedState)) {  
51                             // If the post failed, unpress right now  
52                             mUnsetPressedState.run();  
53                         }  
54                         removeTapCallback();  
55                     }  
56                     break;  
57                 case MotionEvent.ACTION_DOWN:  
58                     if (mPendingCheckForTap == null) {  
59                         mPendingCheckForTap = new CheckForTap();  
60                     }  
61                     mPrivateFlags |= PREPRESSED;  
62                     mHasPerformedLongPress = false;  
63                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
64                     break;  
65                 case MotionEvent.ACTION_CANCEL:  
66                     mPrivateFlags &= ~PRESSED;  
67                     refreshDrawableState();  
68                     removeTapCallback();  
69                     break;  
70                 case MotionEvent.ACTION_MOVE:  
71                     final int x = (int) event.getX();  
72                     final int y = (int) event.getY();  
73                     // Be lenient about moving outside of buttons  
74                     int slop = mTouchSlop;  
75                     if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
76                             (y < 0 - slop) || (y >= getHeight() + slop)) {  
77                         // Outside button  
78                         removeTapCallback();  
79                         if ((mPrivateFlags & PRESSED) != 0) {  
80                             // Remove any future long press/tap checks  
81                             removeLongPressCallback();  
82                             // Need to switch from pressed to not pressed  
83                             mPrivateFlags &= ~PRESSED;  
84                             refreshDrawableState();  
85                         }  
86                     }  
87                     break;  
88             }  
89             return true;  
90         }  
91         return false;  
92     }  

相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧: 
1     public boolean performClick() {  
2         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
3         if (mOnClickListener != null) {  
4             playSoundEffect(SoundEffectConstants.CLICK);  
5             mOnClickListener.onClick(this);  
6             return true;  
7         }  
8         return false;  
9     }  
可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:
    public void setOnClickListener(OnClickListener l) {  
        if (!isClickable()) {  
            setClickable(true);  
        }  
        mOnClickListener = l;  
    }  
  
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!