android中,觸摸事件的傳遞過程主要涉及三個方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
詳細了解這三個方法的作用首先要了解以下幾個知識點:
- android中的Touch事件都是從ACTION_DOWN開始的:
單指:ACTION_DOWN->ACTION_MOVE->ACTION_UP;
多指:ACTION_DOWN->ACTION_POINTER_DOWN->ACTION_MOVE->ACTION_POINTER_UP->ACTION_UP - 一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,可以為0個。當觸摸事件被攔截時,Up可能是0個。
- View在ViewGroup內,ViewGroup也可以在其他ViewGroup內,這時候把內部的ViewGroup當成View來分析。
知道了以上基本知識點以后,就可以開始了~
Activity、ViewGroup、View里的回調方法
在Activity里,有兩個回調方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
在ViewGroup里,有三個回調方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onInterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
在View里,和Activity相同,同樣有兩個回調方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
總結起來就是:
- 和事件分發相關的方法共有三個:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
- dispatchTouchEvent和onTouchEvent在Activity、ViewGroup和View中均存在
- 只有ViewGroup中有onInterceptTouchEvent方法
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的區別
- dispatchTouchEvent:這個方法用來分發TouchEvent。
- onInterceptTouchEvent:這個方法用來攔截TouchEvent。
-
onTouchEvent:這個方法用來處理TouchEvent。
一個簡單的圖可以表示這三個方法的執行過程:
3.png
舉個例子:使用下面這段代碼的xml布局
<com.chiaro.view.ChiaroLinearLayout
android:id="@+id/chiaroLinearLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#999999"
android:padding="80dp"
android:layout_height="match_parent">
<com.chiaro.view.ChiaroTextView
android:id="@+id/chiaroTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="TextView"
android:background="#00dddd"
android:textSize="32sp"/>
</com.chiaro.view.ChiaroLinearLayout>
這個布局長這樣:節點層次很簡單,一個LinearLayout中添加了一個TextView。
MainActivity.java的代碼:
// import的包不貼了……
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("demo","MainActivity-----------onTouchEvent--------------" + event.toString());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("demo","MainActivity-----------dispatchTouchEvent--------" + event.toString());
return super.dispatchTouchEvent(event);
}
}
ChiaroLinearLayout.java的代碼:
// import的包不貼了……
public class ChiaroLinearLayout extends LinearLayout {
// 構造方法省略不貼了……
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.e("demo", "ChiaroLinearLayout-----onInterceptTouchEvent-----" + event.toString());
return super.onInterceptTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("demo","ChiaroLinearLayout-----dispatchTouchEvent--------" + event.toString());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("demo","ChiaroLinearLayout-----onTouchEvent--------------" + event.toString());
return super.onTouchEvent(event);
}
}
ChiaroTextView.java的代碼:
// import的包不貼了……
public class ChiaroTextView extends TextView {
// 構造方法省略不貼了……
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("demo", "ChiaroTextView---------onTouchEvent--------------" + event.toString());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("demo","ChiaroTextView---------dispatchTouchEvent--------" + event.toString());
return super.dispatchTouchEvent(event);
}
}
可以看出,這段代碼只是簡單的打出所有的log。直接運行并點擊一下TextView可以看到log如下。可以看到,這個ACTION_DOWN事件一直傳遞到了ChiaroTextView,但是最終是被MainActivity的onTouchEvent處理的,但是ACTION_UP只傳遞到了MainActivity,最終也是由MainActivity處理的。
…… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroTextView---------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroTextView---------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
…… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
…… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
…… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_UP ……
詳細分析
onInterceptTouchEvent-事件攔截
onInterceptTouchEvent這個方法的返回值是最簡單的,及是否攔截事件,放在最前面講。
- 如果返回值是true,代表事件在當前的viewGroup中會被處理,向下傳遞之路被截斷(所有子控件將沒有機會參與Touch事件),同時把事件傳遞給當前的控件的onTouchEvent()繼續進行傳遞或處理。
- 如果返回值是false,即不攔截當前傳遞來的事件,會繼續向下傳遞,把事件交給子控件的onInterceptTouchEvent()。
如果將ChiaroLinearLayout中的onInterceptTouchEvent方法的返回值改為true,則log為:
…… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_DOWN ……
// 這里的ChiaroTextView沒了 因為被父控件ChairoLinearLayout攔截了
…… E/demo: ChiaroLinearLayout-----onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
…… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
…… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
…… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_UP ……
可以看到,這個事件在ChiaroLinearLayout就被打斷了,沒有繼續傳遞給ChiaroTextView,而是由ChiaroLinearLayout的onTouchEvent繼續傳遞給MainActivity的OnTouchEvent,最終由MainActivity的OnTouchEvent處理了。
onTouchEvent-事件處理
- 如果返回值是true,表示消費(consume)了這個事件。以ACTION_DOWN為例,如果某個控件的onTouchEvent返回值為true,則后續的n個ACTION_MOVE與1個ACTION_UP都會逐層傳遞到這個控件的onTouchEvent進行處理。
- 由于觸摸事件都是連續的,所以這個地方感謝網友【@再不侃】的提問,要特別說明一下。如果ACTION_MOVE傳遞到子控件,而子控件的onTouchEvent返回值是false,即沒有處理該ACTION_MOVE事件,則后續的ACTION_UP就不會傳到該子控件來了。這個原因就造成了最開始沒有對代碼進行任何變更時,ACTION_DOWN事件一直傳遞到了ChiaroTextView,但是最終是被MainActivity的onTouchEvent處理的,而且ACTION_UP只傳遞到了MainActivity,最終也是由MainActivity處理的。的情況。
- 這里要注意是逐層,也就是說每層的攔截器還是可以攔截到后續的ACTION_MOVE與ACTION_UP。如果后續的ACTION_MOVE與ACTION_UP被某層的攔截器攔截,則后續的事件將不會再傳遞給之前處理onTouchEvent的子控件,而是逐層傳遞給由攔截消息的這個控件的onTouchEvent函數進行處理,并且會向其之前接收事件的子控件發送一個ACTION_CANCEL,表示后續事件被取消了。
- 如果返回值是false,則會將ACTION_DOWN傳遞給其父ViewGroup的onTouchEvent進行處理,直到由哪一層ViewGroup消費了ACTION_DOWN事件為止。
如果講上面的代碼還原,并且將ChiaroTextView的onTouchEvent方法的返回值改為true,則log:
…… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroTextView---------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
…… E/demo: ChiaroTextView---------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
…… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
…… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
…… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_UP ……
…… E/demo: ChiaroTextView---------dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
…… E/demo: ChiaroTextView---------onTouchEvent--------------MotionEvent { action=ACTION_UP ……
可以看到,所有的事件都傳遞給ChiaroTextView處理了。包括ACTION_DOWN和ACTION_UP。
dispatchTouchEvent-事件分發
dispatchTouchEvent比較復雜,可以按照下面這張圖分析:ViewGroup和View組成了一棵樹形結構,最頂層為Activity的ViewGroup,下面有若干的ViewGroup節點,每個節點之下又有若干的ViewGroup節點或者View節點,依次類推。
當一個Touch事件依次下發,下發的過程是調用子View(ViewGroup)的dispatchTouchEvent方法實現的。簡單來說,就是ViewGroup遍歷它包含著的子View,調用每個View的dispatchTouchEvent方法,而當子View為ViewGroup時,又會通過調用ViewGroup的dispatchTouchEvent方法繼續調用其內部的View的dispatchTouchEvent方法。上述例子中的消息下發順序是這樣的:①-②-⑤-⑥-⑦-③-④。
dispatchTouchEvent方法只負責事件的分發,它擁有boolean類型的返回值,當返回為true時,順序下發會中斷。在上述例子中如果⑤的dispatchTouchEvent返回結果為true,那么⑥-⑦-③-④將都接收不到本次Touch事件。
ViewGroup的dispatchTouchEvent是真正在執行“分發”工作,而View的dispatchTouchEvent方法,并不執行分發工作,或者說它分發的對象就是自己,決定是否把touch事件交給自己處理,而處理的方法,便是onTouchEvent事件,事實上子View的dispatchTouchEvent方法真正執行的代碼是這樣的:
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他處理,在此不管
return onTouchEvent(event);
}
一般情況下,我們不該在普通View內重寫dispatchTouchEvent方法,因為它并不執行分發邏輯。當Touch事件到達View時,我們該做的就是是否在onTouchEvent事件中處理它。