博文出处: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执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(还可能 会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。 onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,onclick方法就不再执行了。我们可以将这种情况看做触摸事件被onTouch方法拦截了,没有继续向onclick传递。 那么view的点击事件是怎么传递的呢?示意图:
我们可以 看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:
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的源码
相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧:
可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:
dispatchTouchEvent的源码如下:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
那么来分析一下: 首先在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 }
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}