本文共 8620 字,大约阅读时间需要 28 分钟。
之前项目中做一个竖直方向的ViewPager效果(详见我的另一篇博文),这几天做了几个改动,突然发现我设置的OnTouchListener对触摸事件的监听突然不起作用了,琢磨了半天觉得问题就出在onTouch的返回值true还是false上,后来自己测试的时候发现不光与这个有关,与OnClickListener也有关,我自己做了一些测试并尝试来进行源码的分析,解析真正的规律和原因所在。
1、一个简单的测试
为了弄懂这个问题,我们先来做一个简单的测试,寻找一下其中的规律,我们在布局中加一个很简单的view,然后给他设置一个OnTouchListener,根据onTouch函数返回值分别测一下结果 (1)全部返回falseView view = findViewById(R.id.id_view_test);view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){ Log.d("test", "DOWN"); } else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){ Log.d("test", "MOVE"); } else if (motionEvent.getAction() == MotionEvent.ACTION_UP){ Log.d("test", "UP"); } return false; } });
然后我们测试一下一次滑动的结果,发现输出只有DOWN操作:
11-05 21:27:46.453 17673-17673/com.test.lee.touchtestapplication D/test: DOWN
(2)DOWN操作时返回true,其他都返回false
public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){ Log.d("test", "DOWN"); return true; //DOWN时返回true } else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){ Log.d("test", "MOVE"); } else if (motionEvent.getAction() == MotionEvent.ACTION_UP){ Log.d("test", "UP"); } return false; }
测试结果发现只要DOWN返回true,无论之后返回什么,直到UP的所有操作都收到了:
11-05 21:33:47.084 17673-17673/com.test.lee.touchtestapplication D/test: DOWN11-05 21:33:47.169 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.188 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.206 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.225 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.243 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.262 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.280 17673-17673/com.test.lee.touchtestapplication D/test: MOVE11-05 21:33:47.284 17673-17673/com.test.lee.touchtestapplication D/test: UP
(3)DOWN时返回false,其他都返回true
public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){ Log.d("test", "DOWN"); return false; } else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){ Log.d("test", "MOVE"); } else if (motionEvent.getAction() == MotionEvent.ACTION_UP){ Log.d("test", "UP"); } return true; }
测试结果和第一个一样,只有DOWN操作:
11-05 21:36:40.789 17673-17673/com.test.lee.touchtestapplication D/test: DOWN
(4)设置了OnClickListener,并且onTouch中DOWN返回false
这是一个很特殊的情况,上面我们也试验了,只要DOWN返回false后面一连串的事件都接受不到,我们尝试设置OnClickListener试试:view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){ Log.d("test", "DOWN"); return false; } else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){ Log.d("test", "MOVE"); } else if (motionEvent.getAction() == MotionEvent.ACTION_UP){ Log.d("test", "UP"); } return false; } });view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d("test", "CLICK"); } });
测试结果发现不仅onClick被调用,onTouch中也收到了从DOWN到UP的所有的事件:
11-05 21:40:18.215 25949-25949/com.test.lee.touchtestapplication D/test: DOWN11-05 21:40:18.260 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.279 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.297 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.316 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.334 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.353 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.371 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.389 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.408 25949-25949/com.test.lee.touchtestapplication D/test: MOVE11-05 21:40:18.424 25949-25949/com.test.lee.touchtestapplication D/test: UP11-05 21:40:18.523 25949-25949/com.test.lee.touchtestapplication D/test: CLICK
(5)设置了OnClickListener,并且onTouch中DOWN返回true
view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){ Log.d("test", "DOWN"); return true; } else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){ Log.d("test", "MOVE"); } else if (motionEvent.getAction() == MotionEvent.ACTION_UP){ Log.d("test", "UP"); } return true; } });view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d("test", "CLICK"); } });
测试结果发现只有onTouch收到了全部点击事件,onClick并没有被调用:
11-05 22:07:50.914 30784-30784/com.test.lee.touchtestapplication D/test: DOWN11-05 22:07:50.981 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:50.998 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.016 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.034 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.053 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.082 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.090 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.103 30784-30784/com.test.lee.touchtestapplication D/test: MOVE11-05 22:07:51.103 30784-30784/com.test.lee.touchtestapplication D/test: UP
2、规律总结
(1)首先没有设置OnClickListener的情况下,onTouch的返回值表示的就是View对点击事件是否消耗,如果在DOWN事件传递过来时返回false,那么剩下的MOVE直到UP的事件都不会被onTouch接收到;如果在DOWN事件返回true,那么剩下的直到UP的事件都会接受到,无论你之后的返回值。 (2)在同时设置了OnTouchListener与OnClickListener之后,情况就有些复杂了: 情况1:如果onTouch在DOWN时返回了true,那么onTouch就和(1)一样收到剩下的所有事件,但onClick就不会被执行; 情况2:如果onTouch在DOWN时返回了false,与(1)不同的是,onTouch尽管在DOWN时返回了false,但之后的所有事件仍能接受到,并且onClick会在之后被调用。3、原因解析
首先解析这个问题其实很简单,就是一个View的关键函数dispatchTouchEvent
,让我们直接来看代码: public boolean dispatchTouchEvent(MotionEvent event){ ... ... if(onFilterTouchEventForSecurity(event)){ ListenerInfo li = mListenerInfo; if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //(1)onTouch调用 return true; } if(onTouchEvent(event)){ //(2)onTouchEvent调用 return true; } } ... ... return false; }
dispatchTouchEvent
就是触摸时间分发的关键函数,相信看过相关解析的一定对这个很清楚,如果一个事件能够传递给当前View,那么一定会调用这个方法,返回值就是表示是否消耗此事件,那么我们来根据上面的规律进行分析:
onTouchEvent
函数,如果设置了OnClickListener,就会在其中进行调用,如果没有设置,dispatchTouchEvent
就会返回false,那么剩下的事件都不会交由此View进行处理; (2)如果同时设置了OnTouchListener与OnClickListener,那么我们再按上面的两种情况进行分析: 情况1:onTouch在DOWN时返回了true,那么代码(1)处就得到了真的结果,直接就返回了true,可以知道后面代码(2)处的onTouchEvent
函数不会被执行,那么自然你的OnClickListener就不起作用了,onClick就不会被执行; 情况2:onTouch在DOWN时返回了false,那么代码(1)处就不会得到真的结果,后面代码(2)处的onTouchEvent
函数就会得到执行,而如果你设置了OnClickListener,View就会处于CLICKABLE状态,那么onTouchEvent
函数就会返回true,dispatchTouchEvent
就会返回true,那么这时后面的事件由于DOWN时返回true,就会统统交由此View进行处理,自然你的onTouch中也能够监听到后面的所有事件!这样上面的情况就能够得到解释了。 4、总结
这样我们就搞清楚了onTouch与onClick两种监听的相互关系,总的来说onTouch优先级更高,如果onTouch在DOWN中返回true,那么onClick就不会执行;但如果onClick被设置,那么一定程度上也会影响onTouch。关于具体的触摸事件传递机制我之后会配合设计模式写一个全面的博文,加强自己的认识和理解。转载地址:http://fdzqi.baihongyu.com/