1. 程式人生 > >Android中onTouch與onClick兩種監聽的完全解析

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。關於具體的觸控事件傳遞機制我之後會配合設計模式寫一個全面的博文,加強自己的認識和理解。