一些問題
什麼是事件?
使用者觸控螢幕的位置等資料封裝成物件MotionEvent,這個物件就是事件
什麼是事件分發?
MotionEvent物件傳遞的過程就叫事件分發
MotionEvent都有可能在哪些物件之間傳遞?
Android
的UI
介面由Activity
、ViewGroup
、View
及其派生類組成,所以MotionEvent在Activity,ViewGroup,View之間傳遞。
事件分發既然是一個過程,就會有順序,那麼MotionEvent傳遞的順序是什麼?
從外向內分發順序為:Activity
-> ViewGroup
-> View
通過上邊一些問題和答案,應該對事件分發學習有一個方向:就是對MotionEvent傳遞的過程的一個分析
Activity
中MotionEvent傳遞過程-
ViewGroup
中MotionEvent傳遞過程 -
View
中MotionEvent傳遞過程
只要把上邊三個傳遞過程原始碼捋順,就算是掌握了事件分發。
MotionEvent傳遞過程由哪些方法協作完成?
dispatchTouchEvent() :事件分發
onInterceptTouchEvent():事件攔截
onTouchEvent():事件處理
他們之間的邏輯關係我們可以用一個虛擬碼去表示:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}複製程式碼
那麼我們首先去看Activity中的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}複製程式碼
MotionEvent首先傳遞給window中的superDispatchTouchEvent方法中, 如果裡邊邏輯處理了MotionEvent,那麼直接返回true 即 Activity中不用處理MotionEvent(就是不會呼叫onTouchEvent方法),返回false則事件交給Activity的onTouchEvent去處理。
我們繼續window中去看這個superDispatchTouchEvent方法邏輯,window類是個抽象類,它的實現類是PhoneWindow,那麼我們就去找PhoneWindow中superDispatchTouchEvent方法。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}複製程式碼
這裡邊呼叫了DecorView的superDispatchTouchEvent方法,我們繼續去到DecorView中
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}複製程式碼
這裡邊又呼叫了父類的事件分發的方法,而DecorView繼承FrameLayout,我們繼續跟蹤,FrameLayout中沒有重寫dispatchTouchEvent方法,我們繼續找父類,到了ViewGroup中的dispatchTouchEvent方法。
到此,我們就把Activity中的MotionEvent傳遞到ViewGroup中。並且根據返回值來判斷是否需要傳遞給Activity中的onTouchEvent方法中。
我們繼續看ViewGroup中的dispatchTouchEvent原始碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
//獲取手勢動作
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
//是否進行攔截
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//ViewGroup中ACTION_DOWN事件處理;
if (action == MotionEvent.ACTION_DOWN) {
//如果mMotionTarget儲存了View,因為是從新開始的DOWN,所以清空;
if (mMotionTarget != null) {
mMotionTarget = null;
}
//是否禁用攔截事件功能,如果禁用了攔截功能(不攔截)或者onInterceptTouchEvent()返回false,說明不攔截事件。
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
//迴圈每一個子View;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
//判斷當前子View是否包含點選區域;
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//呼叫子View的dispatchTouchEvent() ,向子事件分發事件;
//1.如果返回true,說明子類或者子類的某個子類中消費了事件,將這個child記錄到mMotionTarget中,
// 每一個ViewGroup都記錄了消費了這個事件的子View,像鏈式,最後當前ViewGroup返回true,
// 返回給呼叫這個ViewGroup的外層ViewGroup,一直到Activity中的dispatchTouchEvent()中,最後Activity中也返回true;
//2.如果返回false,說明子類或者子類的子類中沒有消費這個事件,所以mMotionTarget不會被賦值,會繼續向下執行程式碼;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
//ACTION_DOWN事件時,被攔截,或者子類沒有消費ACTION_DOWN,呼叫自己的onTouchEvent()執行onTouch、onClick()方法等;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//呼叫super方法即呼叫到View中的dispatchTouchEvent()方法,還是呼叫的當前View,
//在View中會呼叫自己的onTouchEvent()執行onTouch、onClick()方法等處理;
//這裡兩種情況:1.自身沒有處理返回false;2.自身處理了返回true,本身處理了,不會儲存到mMotionTarget中,而它的外層ViewGroup會儲存他;
//true/false都會一直返回到Activity中。
return super.dispatchTouchEvent(ev);
}
//===============================以上完成了事件的分發=======================================================
//在ACTION_UP或者MOVE時,子類中消費了ACTION_DOWN事件,沒有禁用攔截功能(可以攔截),並且攔截事件;
// 無論 target 是否為 null ,ACTION_DOWN事件的處理都不能走到這裡,在之下都是ACTION_MOVE和ACTION_UP的邏輯
// 如果執行到這裡,說明有響應ACTION_DOWN事件的view物件,這就看我們是否被允許攔截和要不要攔截了
// 如果允許攔截並且攔截了ACTION_MOVE和ACTION_UP事件,則將ACTION_CANCEL事件分發給target
// 然後直接返回true,表示已經響應了該次事件,這時當下個事件來臨時候,就是攔截者去處理事件
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
//將子類事件重置為ACTION_CANCEL;
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
// 如果沒有攔截ACTION_MOVE和ACTION_UP事件,則直接派發給target
return target.dispatchTouchEvent(ev);
}複製程式碼
下邊看一下在Viewgroup的dispatchTouchEvent方法中繼續向下分發事件程式碼if(child.dispatchTouchEvent(ev))執行後如果child也是一個ViewGroup子類那麼跟上邊的邏輯是一樣的,那麼如果child是一個View的子類,比如Button、TextView、ImageView等時,是如何分發事件的,即事件在View類中的傳遞流程,接著看一下原始碼:
//View中的dispatchTouchEvent;
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}複製程式碼
在這裡可以看到首先判斷我們有沒有給當前View設定OnTouchListener事件,並且當前View是enabled狀態的,如果前兩個條件都滿足,則會呼叫我們OnTouchListener中的onTouch()方法,如果上邊三個條件都為true,則直接返回true,表明當前View消費當前事件,如果我們在onTouch()方法中返回false,那麼就會跳過判斷執行自己的onTouchEvent()方法,這裡先提前說一下,呼叫onTouchEvent()方法也有繼續判斷當前View是否消費當前事件的作用,在其內部會根據我們點選在控制元件上停留時間判斷是否執行OnClick,OnLongClick事件。