onTouch和 onClick的那些事儿
PS:在这个全民知识焦虑的时代,每个人都应该明白学习的重要性,应该成为“会学习的人”,而不是做“最勤奋的笨蛋”。
事件的分发流程可以说基本上已经阐述清楚,在阅读本篇文章之前,请先阅读下面几篇文章:
还有一个问题是 Android 事件传递过程中 onTouch 和 onClick 事件在整个事件过程中是如何进行事件传递的,下面主要是关于 onTouch 、 onClick 与事件传递过程中调用的先后顺序,将从如下几个方面介绍:
-
源码中的 onTouch() 方法
-
源码中的 onClick() 方法
-
onTouch() 与 onClick() 方法之间的关系
-
总结
源码中的 onTouch() 方法
当要设置触摸事件的监听时,使用到 View 类中的 OnTouchListener 接口,然后通过 setOnClickListener 设置对触摸事件的监听,然后就可以通过具体的事件类型去执行某些操作,onTouch() 方法就是接口 OnTouchListener 中定义的方法,在 View 的 dispatchTouchEvent() 方法中调用,下面时 onTouch方法在源码中的具体调用:
1//事件分发
2public boolean dispatchTouchEvent(MotionEvent event) {
3 ...
4 //默认返回值
5 boolean result = false;
6 ...
7 //注意判断条件
8 if (onFilterTouchEventForSecurity(event)) {
9 if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
10 result = true;
11 }
12
13 ListenerInfo li = mListenerInfo;
14 //注意判断条件
15 if (li != null && li.mOnTouchListener != null
16 && (mViewFlags & ENABLED_MASK) == ENABLED
17 && li.mOnTouchListener.onTouch(this, event)) {
18 result = true;
19 }
20
21 if (!result && onTouchEvent(event)) {
22 result = true;
23 }
24 }
25 ...
26 return result;
27}
上述代码中,先来看一下最外面的条件 onFilterTouchEventForSecurity() 方法,只关心该方法的返回值即可,源码如下:
1/**
2 * 如果事件正常分发返回true,如果事件被丢弃返回false
3 * @see #getFilterTouchesWhenObscured
4 */
5public boolean onFilterTouchEventForSecurity(MotionEvent event) {
6 //noinspection RedundantIfStatement
7 if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
8 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
9 // Window is obscured, drop this touch.
10 return false;
11 }
12 return true;
13}
所以该方法正常情况下返回 true,然后关键的条件主要就是 ListenerInfo 是否为 null,mOnTouchListener 是否为 null,以及 onTouch() 方法的返回值,下面是 ListenerInfo 初始化的源码部分,具体如下:
1//getListenerInfo()的具体调用
2public void setOnTouchListener(OnTouchListener l) {
3 getListenerInfo().mOnTouchListener = l;
4}
5//ListenerInfo的初始化
6ListenerInfo getListenerInfo() {
7 if (mListenerInfo != null) {
8 return mListenerInfo;
9 }
10 mListenerInfo = new ListenerInfo();
11 return mListenerInfo;
12}
显然,当通过 setOnTouchListener() 方法设置触摸事件的监听时就初始化了 ListenerInfo,同在在设置触摸事件监听的时候 mOnTouchListener != null 成立,最后 onTouch() 方法的返回值决定了 dispatchTouchEvent() 方法是否返回 true。所以,当设置了触摸监听事件且 onTouch() 方法返回 true 时,表示事件就此处理也就不再向子 View 传递了,同时,onTouchEvent() 方法也就不再执行,返回 false 则 onTouchEvent() 方法还会执行。
源码中的 onClick() 方法
当要设置点击事件的事件监听时,使用到 View 类中的 OnClickListener 接口,然后通过 setOnClickListener 设置对单击事件的监听,然后就可以通过具体的事件类型去执行某些操作,onClick() 方法就是 OnClickListener 接口中定义的方法,在 View 的 onTouchEvent() 方法中调用,在下文中也会进一步得到验证,下面是 onClick() 方法在源码中的具体调用:
1//事件处理
2public boolean onTouchEvent(MotionEvent event) {
3 ...
4 if (((viewFlags & CLICKABLE) == CLICKABLE ||
5 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
6 (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
7 switch (action) {
8 case MotionEvent.ACTION_UP:
9 ...
10 //如果执行了该方法,其返回值就是onTouchEvent()的返回值
11 performClick();
12 ...
13 break;
14 }
15 return true;
16 }
17 return false;
18}
上面代码中至少找到了 onClick() 方法的调用位置,下面是 performClick() 方法:
1/**
2 * 主要回调了 OnClickListener
3 */
4public boolean performClick() {
5 final boolean result;
6 final ListenerInfo li = mListenerInfo;
7 if (li != null && li.mOnClickListener != null) {
8 playSoundEffect(SoundEffectConstants.CLICK);
9 //调用了OnClickListener接口中的onClick()方法
10 li.mOnClickListener.onClick(this);
11 result = true;
12 } else {
13 result = false;
14 }
15 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
16 return result;
17}
显然,只要我们在代码中通过 setOnClickListener() 方法设置了对单击事件的监听,则对应 View 的 onTouchEvent() 方法返回 true,当然事件就此消费,反之返回 false,那么 onTouch 与 onClick 之间的调用顺序如何,它们之间会互相影响吗,下面就会从案列的角度了解它们之间的关系。
onTouch() 与 onClick() 方法之间的关系
还是之前的案例,MLinearLayout 嵌套 MRelativeLayout,MRelativeLayout 嵌套 MTextView,三个 View 都只是重写了与它们自身相关的事件分发,然后为 MTextView 设置对触摸事件、单击事件的监听,具体如下:
1@Override
2protected void onCreate(Bundle savedInstanceState) {
3 super.onCreate(savedInstanceState);
4 setContentView(R.layout.activity_main);
5
6 findViewById(R.id.textView).setOnTouchListener(new View.OnTouchListener() {
7 @Override
8 public boolean onTouch(View v, MotionEvent event) {
9 Log.i("Event", "TextView-------onTouch---------------return:" + false);
10 return false;
11 }
12 });
13
14 findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
15 @Override
16 public void onClick(View v) {
17 Log.i("Event", "TextView-------onClick");
18 }
19 });
20}
- 让 onTouch() 返回 false,查看日志如下:
结论:设置了对触摸事件的监听,onTouch() 方法 false 时 onTouchEvent() 方法在 onTouch() 方法之后执行,事件就此消费,接着接受 ACTION_DOWN 之后的一系列事件,途中使用鼠标,故没有 ACTION_MOVE 事件,还有在 onTouch() 方法返回 false 的情况下 onClick() 执行了。
- 让 onTouch() 返回 true,查看日志如下:
结论 :当 onTouch() 返回 true 的时候,正如前面所述 onTouchEvent() 将不会再执行,故 onClick() 也就不会再执行。
总结
onTouch() 方法的返回值决定了 onTouchEvent() 方法要不要执行,如果 onTouch() 返回 true,则 onTouchEvent() 不会再执行,返回 false ,则 onTouchEvent() 继续执行,而 onClick() 的回调是在 onTouchEvent() 方法中调用,onTouchEvent() 不执行则 onClick() 不执行。