金九銀十的換工作季又到了。希望那些每天都進(jìn)步的攻城獅們都能擁有一個(gè)美麗而又多金的好工作。
事件分發(fā)機(jī)制一直是面試官喜歡問的題目,所以我們有必要徹底搞清楚。搞清楚了它不僅對我們現(xiàn)在的工作有益,對我們換新工作也百益無一害。
看了網(wǎng)上很多大牛寫的文章,自己同時(shí)也一一對著源碼看了,都說好記性不如爛筆頭,這才有了這篇文章的由來。
首先為了不耽誤你的時(shí)間,我要事先說個(gè)明,我這次分析是從Activity開始,platform-29,不是從Framework或者更底層說起,如果大家有興趣對事件更深層的了解,可以去看看下面這篇文章,它從整個(gè)系統(tǒng)層面進(jìn)行了詳細(xì)的闡述。
先解釋下幾個(gè)名詞
- 事件序列:Android的每一次按下都會是一連串的事件,而不是單個(gè)的,序列主要包括一個(gè)按下事件,0個(gè)或N個(gè)移動事件,一個(gè)抬起事件
- 消費(fèi)事件:當(dāng)前view處理完事件,返回值為true,返回false代表不消費(fèi)
1 首先來張全局的流程圖

看上面的圖你有沒有發(fā)現(xiàn)有個(gè)邏輯沒有講,就是dispatchTouchEvent返回值代表啥意思?好的,我就從這個(gè)切入點(diǎn)講講。
2 對上面的圖解析
先來看Activity的分發(fā)代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我現(xiàn)在寫的每一句都不是廢話,如果你跟不上,請重復(fù)觀看代碼,我保證你一定跟得上。
從上面我們可以看出如果getWindow().superDispatchTouchEvent()這個(gè)方法返回true,它也就返回true了。否則則調(diào)用自己的onTouchEvent()方法。
這個(gè)東東getWindow().superDispatchTouchEvent()又是誰?你要是敢問,我就直接一個(gè)眼神給你,然后大家心照不宣。好吧,本著服務(wù)大眾的心態(tài),你看做人就得心態(tài)好。心態(tài)好了,吃嘛嘛香,擼碼碼順 ╰( ̄▽ ̄)╭
看過幾篇事件分發(fā)機(jī)制文章的人都應(yīng)該知道getWindow其實(shí)是PhoneWindow,
getWindow().superDispatchTouchEvent()源碼
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
看到如此清涼的代碼,是不是感覺好像在7月炎熱的太陽下喝了一瓶透心涼的-雪碧。
真是簡單的不能再簡單了。源碼都這么簡單多好??!我們接著往下跟
mDecor.superDispatchTouchEvent()源碼
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
有沒有感覺到幸福二重奏?又是讓人豁然開朗的一行代碼
這代碼真帶勁,越看越上癮。
到這里,你應(yīng)該能猜到下個(gè)代碼會走到哪里了吧?Right,你猜對了,mDecor是一個(gè)DecorView---FrameLayout的子類。
DecorView源碼
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
于是 ViewGroup的dispatchTouchEvent就進(jìn)入了我們的視野。
由于方法過長,我對其進(jìn)行了裁剪
ViewGroup.dispatchTouchEvent()源碼
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 如果是按下事件,重置一下mFirstTouchTarget這個(gè)對象為null
cancelAndClearTouchTargets(ev);
// 如果是按下事件,重置 攔截Flag
resetTouchState();
}
// 按下或者找到了消費(fèi)事件的view
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//這里先判斷是否設(shè)置了禁用攔截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//沒有設(shè)置禁用攔截情況下,intercepted取決于onInterceptTouchEvent()返回值
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//不是事件流開始的 ACTION_DOWN,也沒有事件流的消費(fèi)組件,那么直接攔截。
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {// 不取消,不攔截,就分發(fā)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 就是這里分發(fā),看子View是否消費(fèi)
// 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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);// 將消費(fèi)事件流的子View的父View(當(dāng)前ViewGroup)記錄在消費(fèi)的鏈表頭
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
// dispatchTransformedTouchEvent方法返回false,意味著子View也不消費(fèi)
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}// DOWN 事件的處理結(jié)束
}
// Dispatch to touch targets.
// mFirstTouchTarget賦值是在通過addTouchTarget方法獲取的;
// 只有處理ACTION_DOWN事件,才會進(jìn)入addTouchTarget方法。
// 這也正是當(dāng)View沒有消費(fèi)ACTION_DOWN事件,則不會接收其他MOVE,UP等事件的原因
if (mFirstTouchTarget == null) { // 子View不消費(fèi),交給自己處理
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);// path 1
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {// path 2
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;// path 3
}
}
...
return handled;
}
重要部分我都加了注釋,依然還有不明白的可以在評論區(qū)at我,我知無不言言無不盡。
重點(diǎn)講一下我加的3個(gè)path,因?yàn)閺牧鞒虉D里看不出這三個(gè)分支怎么判斷的。這里的邏輯判斷比較緊湊,防止你跟不上我的車,請先深呼吸一口氣,Ready ?Go !
首先來看
path 1 :當(dāng)沒有找到消費(fèi)的子view時(shí),handled 的值為dispatchTransformedTouchEvent 返回的值,這個(gè)方法的第三個(gè)參數(shù)傳的是null值,我們跳進(jìn)這個(gè)方法可以看到,如果child為null,那么就返回super.dispatchTouchEvent(transformedEvent)的值,這個(gè)值一定為false,為什么呢?看咱們的前提---當(dāng)沒有找到消費(fèi)的子view,沒有消費(fèi)說明ViewGroup或子view的onTouchEvent一定返回的是false,所以整個(gè)dispatchTouchEvent()返回的值為false,然后它把結(jié)果依次往上傳遞,最后到decorView,然后傳到Activity,getWindow().superDispatchTouchEvent()這里返回了false,那么就會走Activity的onTouchEvent,這個(gè)方法啥也沒干,它也直接返回了false,事件結(jié)束。
path 2和path 3 :找到了消費(fèi)事件的view后, handled能不能為true,關(guān)鍵就看else分之了,handled的值如果為true,我們就反推出dispatchTransformedTouchEvent()這個(gè)方法也返回true,關(guān)鍵就看這個(gè)方法到底返回的值是true還是false呢?這個(gè)方法又依賴于View的 dispatchTouchEvent 方法,索性我們就看看它到底藏著什么玄機(jī)?
為了方便分析,我把不同類的方法放一塊,我會標(biāo)出類名
//ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
// Perform any necessary transformations and dispatch.
if (child == null) {//如果子view為空,返回父view的dispatchTouchEvent方法的返回值
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//如果子view不為空,返回子view的dispatchTouchEvent方法的返回值
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
// View.java
public boolean dispatchTouchEvent(MotionEvent event) {
...
// mOnTouchListener.onTouch優(yōu)先于onTouchEvent。
if (onFilterTouchEventForSecurity(event)) {
//當(dāng)存在OnTouchListener,且視圖狀態(tài)為ENABLED時(shí),調(diào)用onTouch()方法
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true; //如果已經(jīng)消費(fèi)事件,則返回True
}
//如果OnTouch()沒有消費(fèi)Touch事件則調(diào)用OnTouchEvent()
if (!result && onTouchEvent(event)) { // [見小節(jié)2.5.1]
result = true; //如果已經(jīng)消費(fèi)事件,則返回True
}
}
return result;
}
從view的 dispatchTouchEvent方法可以看出,如果消費(fèi)了事件,一定是返回true的,這樣 handled = child.dispatchTouchEvent(transformedEvent);通過這行代碼handled也為true了