自定義控件(視圖)2期筆記14:自定義視圖之View事件分發 dispatchTouchEvent,onTouch,onTouchEvent,onClick邏輯順序過程

1. 這裏咱們先從案例角度說明dispatchTouchEvent,onTouch,onTouchEvent,onClick邏輯順序過程android

(1)首先咱們重寫一個MyButton 繼承自 Button,代碼以下:app

 1 package com.himi.eventdemo;  2 
 3 import android.content.Context;  4 import android.util.AttributeSet;  5 import android.util.Log;  6 import android.view.MotionEvent;  7 import android.widget.Button;  8 
 9 public class MyButton extends Button { 10     
11     private static String TAG = "MyButton"; 12     public MyButton(Context context) { 13         super(context); 14  } 15 
16     public MyButton(Context context, AttributeSet attrs) { 17         super(context, attrs); 18  } 19 
20     public MyButton(Context context, AttributeSet attrs, int defStyleAttr) { 21         super(context, attrs, defStyleAttr); 22  } 23 
24 
25  @Override 26     public boolean dispatchTouchEvent(MotionEvent event) { 27 
28         switch (event.getAction()) { 29             case MotionEvent.ACTION_DOWN: 30                 Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_DOWN"); 31                 break; 32             case MotionEvent.ACTION_MOVE: 33                  Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_MOVE"); 34                 break; 35             case MotionEvent.ACTION_UP: 36                  Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_UP"); 37                 break; 38  } 39 
40         return super.dispatchTouchEvent(event); 41  } 42 
43  @Override 44     public boolean onTouchEvent(MotionEvent event) { 45 
46         switch (event.getAction()) { 47             case MotionEvent.ACTION_DOWN: 48                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_DOWN"); 49                 break; 50             case MotionEvent.ACTION_MOVE: 51                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_MOVE"); 52                 break; 53             case MotionEvent.ACTION_UP: 54                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_UP"); 55                 break; 56  } 57 
58         return super.onTouchEvent(event); 59  } 60 }

(2)來到主佈局文件activity_main.xml,以下:ide

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2  xmlns:tools="http://schemas.android.com/tools"
 3  android:layout_width="match_parent"
 4  android:layout_height="match_parent"
 5  android:gravity="center"
 6  tools:context="com.himi.eventdemo.MainActivity" >
 7     
 8     <com.himi.eventdemo.MyButton  9         android:id="@+id/myButton"
10  android:layout_width="wrap_content"
11  android:layout_height="wrap_content"
12  android:text="測試"
13         />
14         
15 </RelativeLayout>

 

(3)測試MainActivity,以下:源碼分析

 1 package com.himi.eventdemo;  2 
 3 import android.app.Activity;  4 import android.os.Bundle;  5 import android.util.Log;  6 import android.view.MotionEvent;  7 import android.view.View;  8 import android.widget.Button;  9 
10 public class MainActivity extends Activity { 11     
12     private static String TAG ="MainActivity"; 13 
14 
15     private Button myButton; 16 
17 
18  @Override 19     protected void onCreate(Bundle savedInstanceState) { 20         super.onCreate(savedInstanceState); 21  setContentView(R.layout.activity_main); 22 
23         myButton = (Button) findViewById(R.id.myButton); 24 
25         myButton.setOnTouchListener(new View.OnTouchListener() { 26  @Override 27             public boolean onTouch(View v, MotionEvent event) { 28                 switch (event.getAction()) { 29                     case MotionEvent.ACTION_DOWN: 30                          Log.e(TAG,"onTouch====MyButton=====ACTION_DOWN"); 31                         break; 32                     case MotionEvent.ACTION_MOVE: 33                          Log.e(TAG,"onTouch====MyButton=====ACTION_MOVE"); 34                         break; 35                     case MotionEvent.ACTION_UP: 36                          Log.e(TAG,"onTouch====MyButton=====ACTION_UP"); 37                         break; 38  } 39                 return false; 40  } 41  }); 42 
43         myButton.setOnClickListener(new View.OnClickListener() { 44  @Override 45             public void onClick(View v) { 46                 Log.e(TAG,"onClick====MyButton=====onClick"); 47  } 48  }); 49 
50 
51  } 52 
53 
54 }

 

(4)部署程序到手機上,以下:佈局

 

點擊測試按鈕,打印結果以下:post

 

從上面打印的結果分析:測試

點擊Button按鈕事件分發過程以下:this

dispatchTouchEvent --> onTouch --> onTouchEvent --> onClickspa

相信細心的你確定發現了,都是在ACTION_UP事件以後才觸發onClick點擊事件3d

 

 

2. 下面咱們從源碼的角度分析dispatchTouchEvent,onTouch,onTouchEvent,onClick邏輯順序過程

(1)事件分發都是從dispatchTouchEvent方法開始的,那麼咱們這裏是重寫了dispatchTouchEvent方法,而且最後也調用了父類的super.dispatchTouchEvent(event)方法。那麼咱們看看父類中的方法到底作了什麼??點擊進入父類的dispatchTouchEvent方法,發現此方法在View類中找到,其實也不奇怪,全部控件的父類都是View。這裏我貼出最新源碼以下:

 1 public boolean dispatchTouchEvent(MotionEvent event) {  2         boolean result = false;  3 
 4         if (mInputEventConsistencyVerifier != null) {  5             mInputEventConsistencyVerifier.onTouchEvent(event, 0);  6  }  7 
 8         final int actionMasked = event.getActionMasked();  9         if (actionMasked == MotionEvent.ACTION_DOWN) { 10             // Defensive cleanup for new gesture
11  stopNestedScroll(); 12  } 13 
14         if (onFilterTouchEventForSecurity(event)) { 15             //noinspection SimplifiableIfStatement
16             ListenerInfo li = mListenerInfo; 17             if (li != null && li.mOnTouchListener != null
18                     && (mViewFlags & ENABLED_MASK) == ENABLED 19                     && li.mOnTouchListener.onTouch(this, event)) { 20                 result = true; 21  } 22 
23             if (!result && onTouchEvent(event)) { 24                 result = true; 25  } 26  } 27 
28         if (!result && mInputEventConsistencyVerifier != null) { 29             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 30  } 31 
32         // Clean up after nested scrolls if this is the end of a gesture; 33         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest 34         // of the gesture.
35         if (actionMasked == MotionEvent.ACTION_UP ||
36                 actionMasked == MotionEvent.ACTION_CANCEL ||
37                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) { 38  stopNestedScroll(); 39  } 40 
41         return result; 42     }

忽略其餘無關代碼,咱們直接看17--25行。

第17行的 if 判斷關鍵在於li.mOnTouchListener.onTouch(this, event) 的返回值

這個接口回調就是咱們外面寫的myButton.setOnTouchListener事件(Button 的onTouch事件),

在MainActivity代碼裏,咱們setOnTouchListener返回的值是false,因此在源碼中咱們能夠看到 17行的條件不成立,那麼條件不成立,result=false;

所以,源碼的第23行 if 判斷第一個條件成立,繼續執行第二個條件,也就是onTouchEvent。咱們跳到這個方法裏看看裏面幹啥了?看以下代碼:

 1 public boolean onTouchEvent(MotionEvent event) {  2 
 3         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 4                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  5             switch (event.getAction()) {  6                 case MotionEvent.ACTION_UP:  7                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;  8                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {  9                         // take focus if we don't have it already and we should in 10                         // touch mode.
11                         boolean focusTaken = false; 12                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 13                             focusTaken = requestFocus(); 14  } 15 
16                         if (prepressed) { 17                             // The button is being released before we actually 18                             // showed it as pressed. Make it show the pressed 19                             // state now (before scheduling the click) to ensure 20                             // the user sees it.
21                             setPressed(true, x, y); 22  } 23 
24                         if (!mHasPerformedLongPress) { 25                             // This is a tap, so remove the longpress check
26  removeLongPressCallback(); 27 
28                             // Only perform take click actions if we were in the pressed state
29                             if (!focusTaken) { 30                                 // Use a Runnable and post this rather than calling 31                                 // performClick directly. This lets other visual state 32                                 // of the view update before click actions start.
33                                 if (mPerformClick == null) { 34                                     mPerformClick = new PerformClick(); 35  } 36                                 if (!post(mPerformClick)) { 37                    performClick(); 38  } 39  } 40  } 41 
42                         if (mUnsetPressedState == null) { 43                             mUnsetPressedState = new UnsetPressedState(); 44  } 45 
46                         if (prepressed) { 47  postDelayed(mUnsetPressedState, 48  ViewConfiguration.getPressedStateDuration()); 49                         } else if (!post(mUnsetPressedState)) { 50                             // If the post failed, unpress right now
51  mUnsetPressedState.run(); 52  } 53 
54  removeTapCallback(); 55  } 56                     break; 57             return true; 58  } 59 
60         return false; 61     }

咱們看看這裏邊都作了些什麼,忽略其餘,咱們直接看37行的 performClick(); 方法,跳進去繼續看:

(注意:這裏的performClick方法是在ACTION_UP手勢裏邊執行的哦!!!

 1 public boolean performClick() {  2         final boolean result;  3         final ListenerInfo li = mListenerInfo;  4         if (li != null && li.mOnClickListener != null) {  5  playSoundEffect(SoundEffectConstants.CLICK);  6             li.mOnClickListener.onClick(this);  7             result = true;  8         } else {  9             result = false; 10  } 11 
12  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 13         return result; 14     }

看見沒??

第6行 li.mOnClickListener.onClick(this);

這個接口回調就是咱們Button的 onClick事件。到此爲止,咱們從源碼分析了Button事件分發過程。


結論:

dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。而且若是仔細的你會發現,在onTouchEvent方法內部判斷執行onClick方法,可是,在全部ACTION_UP事件以後才觸發onClick點擊事件

 

3. 如今咱們來看看其餘狀況:當onTouch返回爲true,打印結果以下:

 1 package com.himi.eventdemo;  2 
 3 import android.app.Activity;  4 import android.os.Bundle;  5 import android.util.Log;  6 import android.view.MotionEvent;  7 import android.view.View;  8 import android.widget.Button;  9 
10 public class MainActivity extends Activity { 11     
12     private static String TAG ="MainActivity"; 13 
14 
15     private Button myButton; 16 
17 
18  @Override 19     protected void onCreate(Bundle savedInstanceState) { 20         super.onCreate(savedInstanceState); 21  setContentView(R.layout.activity_main); 22 
23         myButton = (Button) findViewById(R.id.myButton); 24 
25         myButton.setOnTouchListener(new View.OnTouchListener() { 26  @Override 27             public boolean onTouch(View v, MotionEvent event) { 28                 switch (event.getAction()) { 29                     case MotionEvent.ACTION_DOWN: 30                          Log.e(TAG,"onTouch====MyButton=====ACTION_DOWN"); 31                         break; 32                     case MotionEvent.ACTION_MOVE: 33                          Log.e(TAG,"onTouch====MyButton=====ACTION_MOVE"); 34                         break; 35                     case MotionEvent.ACTION_UP: 36                          Log.e(TAG,"onTouch====MyButton=====ACTION_UP"); 37                         break; 38  } 39                 return true; 40  } 41  }); 42 
43         myButton.setOnClickListener(new View.OnClickListener() { 44  @Override 45             public void onClick(View v) { 46                 Log.e(TAG,"onClick====MyButton=====onClick"); 47  } 48  }); 49 
50 
51  } 52 
53 
54 }

打印結果以下:

 結論:

dispatchTouchEvent---->onTouch

 

驚奇的發現,居然沒有執行onClick事件是吧????若是你仔細閱讀上面的文章,估計你知道爲何了吧?仍是跟你們一塊兒分析一下吧:源碼以下:

 1 public boolean dispatchTouchEvent(MotionEvent event) {  2         boolean result = false;  3 
 4         if (mInputEventConsistencyVerifier != null) {  5             mInputEventConsistencyVerifier.onTouchEvent(event, 0);  6  }  7 
 8         final int actionMasked = event.getActionMasked();  9         if (actionMasked == MotionEvent.ACTION_DOWN) { 10             // Defensive cleanup for new gesture
11  stopNestedScroll(); 12  } 13 
14         if (onFilterTouchEventForSecurity(event)) { 15             //noinspection SimplifiableIfStatement
16             ListenerInfo li = mListenerInfo; 17             if (li != null && li.mOnTouchListener != null
18                     && (mViewFlags & ENABLED_MASK) == ENABLED 19                     && li.mOnTouchListener.onTouch(this, event)) { 20                 result = true; 21  } 22 
23             if (!result && onTouchEvent(event)) { 24                 result = true; 25  } 26  } 27 
28         if (!result && mInputEventConsistencyVerifier != null) { 29             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 30  } 31 
32         // Clean up after nested scrolls if this is the end of a gesture; 33         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest 34         // of the gesture.
35         if (actionMasked == MotionEvent.ACTION_UP ||
36                 actionMasked == MotionEvent.ACTION_CANCEL ||
37                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) { 38  stopNestedScroll(); 39  } 40 
41         return result; 42     }

從第 17 行能夠看出,條件成立,result=true;

那麼第 23 行 if 條件根本不會執行第二個判斷,那麼就不會執行onTouchEvent方法,也就不會調用 onClick的接口,所以Button 不會執行setOnClickListener中的onClick事件。

 

4. 總結:

 

 

那麼咱們繼續迴歸dispatchTouchEvent中不是ViewGroup的情形
接下來,系統會自動判斷咱們是否實現了onTouchListener 這裏就開始有分支了
--1>. 當咱們實現了onTouchListener,那麼下一步咱們的事件叫交給了onTouchListener.onTouch來處理,這裏就又開始了分支
(1)若是咱們在onTouch中 返回了true,那麼就代表咱們的onTouchListener 已經消化掉了本次的事件,本次事件完結。這就是爲何咱們在onTouch中返回去就永運不會執行onClick,onLongClick了
(2)若是咱們在onTouch中 返回了false,那麼很明顯了咱們的事件就會被onTouchEvent處理
--2>. 同理,當咱們沒有實現了onTouchListener,很明顯了咱們的事件就會被onTouchEvent處理。
異曲同工,最終若是咱們的事件沒有被幹掉,最終都交給了onTouchEvent。那麼接下來咱們繼續來看onTouchEvent, 那麼咱們的onTouchEvent又是用來幹什麼的呢(這裏既然已經有onTouchListener了,他們彷佛如出一轍啊)?
其實否則,說白了咱們的 onTouchEvent最終會用來分發onClickonLongClick事件
相關文章
相關標籤/搜索