昨天晚上從源碼角度複習了一下android的事件分發機制,今天将筆記整理下放在網上。其實說複習,也是按着《android開發藝術探索》這本書作者的思路做的筆記。
目錄
- 事件是如何從Activity傳遞到ViewGroup中的
- ViewGoup對事件做了哪些操作
- 事件在View中的處理
1. 事件是如何從Activity傳遞到ViewGroup中的
android的事件分發過程大緻為Activity-->ViewGroup-->View,當我們點選螢幕的時候産生事件,系統會調用的Activity
dispatchTouchEvent()
開始事件的傳播過程,下面我們看一下Activity的dispatchTouchEvent()源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//當使用者操作了按鍵或者觸摸了螢幕,就會回調用該函數
onUserInteraction();
}
//調用window的superDispatchTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity的dispatchTouchEvent()在它的ACTION_DOWN事件中調用
onUserInteraction()
這個函數可以用來監聽使用者的一些按鍵和觸摸操作。然後就會調用window的
superDispatchTouchEvent()方法
将事件傳播到window中,如果window的superDispatchTouchEvent()傳回true則代表事件被消耗了,反之代表事件沒有被處理,這個時候Activity就會調用自己的onTouchEvent()來處理。
事件傳播到Window中該類是一個抽象類,它的superDispatchTouchEvent()方法是一個抽象方法。我們需要檢視它的實作類
PhoneWindow
中檢視事件的執行過程,下面為PhoneWindow的superDispatchTouchEvent()源碼:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//将事件傳遞給DecorView
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow的superDispatchTouchEvent()中就一行代碼,就是将事件傳遞給
頂層View
的superDispatchTouchEvent()。DecorView有将該方法傳遞給他父類
dispatchTouchEvent()
。如果讀者對DecorView有所了解,應該知道DecorView是FrameLayout的一個子類,也就是說事件傳遞到了ViewGroup的dispatchTouchEvent()方法中了。
2. ViewGoup對事件做了哪些操作
ViewGroup的dispatchTouchEvent()方法代碼很長,我們先看下面一本分代碼:
if (actionMasked == MotionEvent.ACTION_DOWN) {
//将之前的狀态清除
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//預設情況下他們兩個比較的結果為0
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
從上面的代碼我們可以知道,如果ViewGroup分發的是ACTION_DOWN事件,那麼FLAG_DISALLOW_INTERCEPT這個标記位将會被重置,即使子View通過
requestDisallowInterceptTouchEvent()
設定了該标記為。也就是說分發下來的是ACTION_DOWN事件的話ViewGroup将會調用自己的
onInterceptTouchEvent()
尋問是否攔截。如果ViewGroup不攔截ACTION_DOWN事件,那麼事件将會向它的子View傳遞:
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--){
...
//通過dispatchTransformedTouchEvent()将事件傳遞給子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//對mFirstTouchTarget指派
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
ViewGroup首先會周遊子所有View,然後判斷子元素是否能接受這個點選事件。主要是通過兩點,子元素是否在播放動畫和點選事件的着落點是否在子元素的區域内。如果滿足上面的兩點那麼事件将會傳遞給他處理。
dispatchTransformedTouchEvent()
實際上就是子元素的dispatchTouchEvent()方法。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
如果子元素的dispatchTounchEvnent()傳回的為true,那麼
mFirstTouchTarget
将會指派并且跳出循環,如果為false将會進入下次循環繼續周遊子View。
//對mFirstTouchTarget指派
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
下面為addTouchTarget()函數的代碼:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//mFirstTouchTarget為連結清單結構
mFirstTouchTarget = target;
return target;
}
mFirstTouchTarget是否指派,将會影響ViewGroup的攔截政策。如果mFirstTouchTarget為null,那麼ViewGroup将會攔截下來同一序列的所有事件。那mFirstTouchTarget在什麼情況下才為null呢?一般在兩種情況下,要麼是ViewGroup周遊了所有的子元素事件沒有被處理;要麼是子元素處理了ACTION_DOWN但是dispatchTouchEvent傳回為false。這兩種情況下ViewGroup會處理該事件,并且後續的事件也将交給他處理不再向子元素傳遞。
if (mFirstTouchTarget == null) {
// 出入的第三個參數為null,代表事件交給ViewGroup自己處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
3. 事件在View中的處理
View的事件處理比ViewGroup要簡單,他首先會判斷是否設定是否設定了onTouchListener()函數。
下面為View的dispatchTouchEvent()部分代碼:
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//首先判斷是否設定了onTouchListener()
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//調用onTouchEvent(event)
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
從上面代碼可知,如果我們設定了onTouchListener()那麼它會調用onTouch()方法,并且onTouch()傳回值将會影響對View的onTouchEvent(event)函數的調用,如果傳回true将不會調用。由此可見onTouch()的優先級要高于onTouchEvent()。
public boolean onTouchEvent(MotionEvent event) {
...
//如果View設有代理,将會執行TouchDelegate.onTouchEvent(event)
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//隻要View的CLICKABLE和LONG_CLICKABLE有一個傳回true,他就會被消耗這個事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//點選事件
if (!post(mPerformClick)) {
performClick();
}
}
...
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
//長安事件
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
...
break;
...
}
return true;
}
return false;
}
從上面的代碼可以看出,View的點選事件是在ACTION_UP事件中調用了
performClick()
方法處理,長按事件是在ACTION_DOWN事件中調用了
checkForLongClick()
方法處理。
總結
文章到這裡也把android的分發機制從源碼的角度分析的差不多了,讀者應該對android的事件分發機制應該會有有一個比較全面的了解了。下篇文章我将記錄一下關于事件沖突的筆記,如果感興趣可以繼續關注。
參考
Android藝術開發探索第三章————View的事件體系(下)