昨天晚上從源碼角度複習了一下android的事件分發機制,今天將筆記整理下放在網上。其實說複習,也是按着《android開發藝術探索》這本書做者的思路作的筆記。android
android的事件分發過程大體爲Activity-->ViewGroup-->View,當咱們點擊屏幕的時候產生事件,系統會調用的ActivitydispatchTouchEvent()
開始事件的傳播過程,下面咱們看一下Activity的dispatchTouchEvent()源碼:bash
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()來處理。ide
事件傳播到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()方法中了。post
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傳遞:ui
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()方法。this
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
複製代碼
若是子元素的dispatchTounchEvnent()返回的爲true,那麼mFirstTouchTarget
將會賦值而且跳出循環,若是爲false將會進入下次循環繼續遍歷子View。spa
//對mFirstTouchTarget賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
複製代碼
下面爲addTouchTarget()函數的代碼:.net
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);
}
複製代碼
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的事件分發機制應該會有有一個比較全面的瞭解了。下篇文章我將記錄一下關於事件衝突的筆記,若是感興趣能夠繼續關注。