Android中onTouch与onClick两种监听的完全解析

之前项目中做一个竖直方向的ViewPager效果(详见我的另一篇博文),这几天做了几个改动,突然发现我设置的OnTouchListener对触摸事件的监听突然不起作用了,琢磨了半天觉得问题就出在onTouch的返回值true还是false上,后来自己测试的时候发现不光与这个有关,与OnClickListener也有关,我自己做了一些测试并尝试来进行源码的分析,解析真正的规律和原因所在。

1、一个简单的测试 
为了弄懂这个问题,我们先来做一个简单的测试,寻找一下其中的规律,我们在布局中加一个很简单的view,然后给他设置一个OnTouchListener,根据onTouch函数返回值分别测一下结果 
(1)全部返回false

View 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;
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

然后我们测试一下一次滑动的结果,发现输出只有DOWN操作:

11-05 21:27:46.453 17673-17673/com.test.lee.touchtestapplication D/test: DOWN
  • 1

(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;
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

测试结果发现只要DOWN返回true,无论之后返回什么,直到UP的所有操作都收到了:

11-05 21:33:47.084 17673-17673/com.test.lee.touchtestapplication D/test: DOWN
11-05 21:33:47.169 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.188 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.206 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.225 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.243 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.262 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.280 17673-17673/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:33:47.284 17673-17673/com.test.lee.touchtestapplication D/test: UP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(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;
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

测试结果和第一个一样,只有DOWN操作:

11-05 21:36:40.789 17673-17673/com.test.lee.touchtestapplication D/test: DOWN
  • 1

(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");
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

测试结果发现不仅onClick被调用,onTouch中也收到了从DOWN到UP的所有的事件:

11-05 21:40:18.215 25949-25949/com.test.lee.touchtestapplication D/test: DOWN
11-05 21:40:18.260 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.279 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.297 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.316 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.334 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.353 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.371 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.389 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.408 25949-25949/com.test.lee.touchtestapplication D/test: MOVE
11-05 21:40:18.424 25949-25949/com.test.lee.touchtestapplication D/test: UP
11-05 21:40:18.523 25949-25949/com.test.lee.touchtestapplication D/test: CLICK
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(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");
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

测试结果发现只有onTouch收到了全部点击事件,onClick并没有被调用:

11-05 22:07:50.914 30784-30784/com.test.lee.touchtestapplication D/test: DOWN
11-05 22:07:50.981 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:50.998 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.016 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.034 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.053 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.082 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.090 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.103 30784-30784/com.test.lee.touchtestapplication D/test: MOVE
11-05 22:07:51.103 30784-30784/com.test.lee.touchtestapplication D/test: UP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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;  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

dispatchTouchEvent就是触摸时间分发的关键函数,相信看过相关解析的一定对这个很清楚,如果一个事件能够传递给当前View,那么一定会调用这个方法,返回值就是表示是否消耗此事件,那么我们来根据上面的规律进行分析: 
(1)如果没有设置OnClickListener,只设置了OnTouchListener,那么在代码(1)处就会调用onTouch,如果DOWN事件时返回了true,那么剩下的事件都会交由此View进行处理;如果返回了false,那么就会执行代码(2)处的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。关于具体的触摸事件传递机制我之后会配合设计模式写一个全面的博文,加强自己的认识和理解。

猜你喜欢

转载自blog.csdn.net/tklwj/article/details/81060714