Android事件分發機制
一.初識
1.1 用戶對螢屏的操作的事件可以劃分為3種最基礎的事件:
1.ACTION_DOWN:手指剛接觸螢屏,按下去的那一瞬間產生該事件
2.ACTION_MOVE:手指在螢屏上移動時候產生該事件
3.ACTION_UP:手指從螢屏上松開的瞬間產生該事件
1.2 用戶對螢屏的操作最終可以劃分為這三種事件,用戶的ACTION_DOWN到ACTION_UP的操作可以稱為一個事件序列
一個事件序列主要有以下兩種組成:
一: ACTION_DOWN->ACTION_UP
二 :ACTION_DOWN->許多個ACTION_MOVE>ACTION_UP

1.3 Android 的事件分發機制大體可以分為三部分 事件生產 事件分發 事件消費 事件的生產是由用戶點擊螢屏產生,這篇文章著重分析事件的分發和消費,因為事件分發和處理聯系的過于緊密,這篇文章將把事件的分發和消費放在一起分析
在Activity上的事件分發和Activity PhoneView DecorView ViewGroup view 密不可分, 其中 Activity PhoneView DecorView ViewGroup view的關系可以用如下圖來描述:

若干GroupView和若干View組成的控制元件樹可以用下午來概括:

1.4 事件分發的大概流程可以這樣來描述:Activity -> PhoneWindow ->DecorView(DecorView其實就是一種ViewGroup) ->View
1.5 事件分發需要的三個重要方法來共同完成:
public boolean dispatchTouchEvent(event):用于進行點擊事件的分發
public boolean onInterceptTouchEvent(event):用于進行點擊事件的攔截
public boolean onTouchEvent(event):用于處理點擊事件
三個函式的引數均為even,即上面所說的3種型別的輸入事件,回傳值均為boolean 型別
上面的三種方法的呼叫關系大致可以用下面的偽代碼來描述
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消費
if (onInterceptTouchEvent(ev)){//呼叫onInterceptTouchEvent判斷是否攔截事件
consume = onTouchEvent(ev);//如果攔截則呼叫自身的onTouchEvent方法
}else{
consume = child.dispatchTouchEvent(ev);//不攔截呼叫子View的dispatchTouchEvent方法
}
return consume;//回傳值表示事件是否被消費,true事件終止,false呼叫父View的onTouchEvent方法
}
事件的分發到處理的程序大致可以用一個U形圖來描述:

- 首先是要說明的是,上圖的分析僅僅只限于對ACTION_DOWN的分析
- U形圖從上到下可以分為三層,分別是Activity層,ViewGroup層,和View層
- 事件的開始分發是從右上角的大紅色的箭頭開始傳輸的
- 線上的false/ture/super 分別表示的是箭頭起點函式的回傳值:return false/return ture/return supperxxx(呼叫父類的回傳)
- 箭頭指向消費,表示該事件就到此為止,不會再往下傳或往上一級傳
- 如果事件在分發的程序中一直不被消費,那么整個分發的程序看起來就是一個U形的結構
- 圖中只是說明了activity中只有一個ViewGroup的情況,實際操作中可能會有View ViewGroup多層嵌套的情況,原理也是這個原理,大同小異,
總結一下同種分發的幾條規律: - 對于activity來說dispatchTouchEvent中無論是回傳ture/false都會將事件消費,即不會再往下面傳播,只有return super的時候才會傳到 ViewGroup()
- 對于dispatchTouchEvent和onTouchEvent來說return false會把該事件交給父容器來處理
- 對于onInterceptTouchEvent來說回傳false,顧名思義,該viewGroup將不會攔截該事件,這個事件就可以繼續向下傳播了,如果onInterceptTouchEvent回傳ture就表明該ViewGroup將會攔截該事件,事件將會交給該組件的onTouchEvent來處理,事件也就不會再往下面的組件分發了
- 對于onTouchEvent來說對于傳入他的事件,如果回傳ture就代表該事件已經被消費,則流程就終止,如果回傳false,那么就代表該事件將不會由他處理,將會將給父容器的onTouchEvent來處理,
- 每個ViewGroup每次在做分發的時候,問一問攔截器要不要攔截(也就是問問自己這個事件要不要自己來處理)如果要自己處理那就在onInterceptTouchEvent方法中 return true就會交給自己的onTouchEvent的處理,如果不攔截就是繼續往子控制元件往下傳,默認是不會去攔截的,因為子View也需要這個事件,所以onInterceptTouchEvent攔截器return super.onInterceptTouchEvent()和return false是一樣的,是不會攔截的,事件會繼續往子View的dispatchTouchEvent傳遞,
- 看下ViewGroup 的dispatchTouchEvent,之前說的return true是終結傳遞,return false 是回溯到父View的onTouchEvent,然后ViewGroup怎樣通過dispatchTouchEvent方法能把事件分發到自己的onTouchEvent處理呢,return true和false 都不行,那么只能通過Interceptor把事件攔截下來給自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默認實作就是去呼叫onInterceptTouchEvent
- 那么對于View的dispatchTouchEvent return super.dispatchTouchEvent()的時候呢事件會傳到哪里呢,很遺憾View沒有攔截器,但是同樣的道理return true是終結,return false 是回溯會父類的onTouchEvent,怎樣把事件分發給自己的onTouchEvent處理呢,那只能return
super.dispatchTouchEvent,View類的dispatchTouchEvent()方法默認實作就是能幫你呼叫View自己的onTouchEvent方法的,
1.6 對ACTION_MOVE 和ACTION_UP事件的處理
- 對這兩種事件的處理可以總結為下面這些內容ACTION_DOWN事件在哪個控制元件消費了(return true), 那么ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent)做事件分發往下傳,就只會傳到這個控制元件,不會繼續往下傳,如果ACTION_DOWN事件是在dispatchTouchEvent消費,那么事件到此為止停止傳遞,如果ACTION_DOWN事件是在onTouchEvent消費的,那么會把ACTION_MOVE或ACTION_UP事件傳給該控制元件的onTouchEvent處理并結束傳遞,(下面的三張圖均來自圖解 Android 事件分發機制 紅色的箭頭代表ACTION_DOWN 事件的流向藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向)



二.原始碼分析
1. 從Activity到ViewGroup
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
......
// ->>分析1
if (getWindow().superDispatchTouchEvent(ev)) { //getWindow回傳的是一個PhoneWindow
//物件 ,即這里呼叫的是PhoneWindow
// 的superDispatchTouchEvent,也就
//說在這里Activity把時間傳給了
//PhoneWindow
return true;
// 若getWindow().superDispatchTouchEvent(ev)的回傳true
// 則Activity.dispatchTouchEvent()就回傳true,則方法結束,即 :該點擊事件停止往下傳遞 & 事件傳遞程序結束
// 否則:繼續呼叫Activity.onTouchEvent
}
return onTouchEvent(ev);
}
// :當一個點擊事件未被Activity下任何一個View接收/處理時,就會呼叫該方法
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor的型別是DecorView,DecorView是PhoneWindow的內部類,繼承自FrameLayout,而FrameLayout是ViewGroup的子類,
// 所以mDecor是一個頂級的ViewGroup,在這里就實作了事件從PhoneWindow到頂級
//ViewGroup的傳遞
}

DecorView:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 在這里它呼叫了父類的dispatchTouchEvent,而DecorView的父類是FrameLayout,而
// FrameLayout的父類是ViewGroup,到這里就是我們熟悉的ViewGroup 的dispatchTouchEvent
}
總結
當一個點擊事件發生時,從Activity的事件分發開始(Activity.dispatchTouchEvent()),流程總結如下:

1. ViewGroup中的分發
1.1
class ViewGroup:
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//清除FLAG_DISALLOW_INTERCEPT設定并且mFirstTouchTarget 設定為null
resetTouchState();
}
// Check for interception.
final boolean intercepted;//是否攔截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) { // 注意這里 -> 分析一
//FLAG_DISALLOW_INTERCEPT是子View通過
//requestDisallowInterceptTouchEvent方法進行設定的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 分析二
if (!disallowIntercept) {
//呼叫onInterceptTouchEvent方法判斷是否需要攔截
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.
intercepted = true;
}
...
}
分析一
這里首先判斷事件是否為DOWN事件,如果是,則進行初始化,resetTouchState方法中會把 mFirstTouchTarget的值置為null,這里為什么要進行初始化呢?原因就是一個完整的事件序列是以 DOWN開始,以UP結束的,所以如果是DOWN事件,那么說明這是一個新的事件序列,故而需要初 始化之前的狀態,接著往下看,上面代碼注釋1處的條件如果滿足,則執行下面的句子,mFirstTouchTarget 的意義是:當 前ViewGroup 是否攔截了事件,如果攔截了, mFirstTouchTarget=null;如果沒有攔截并交由子View來處 理,mFirstTouchTarget!=null 從上面代碼我們可以看出,ViewGroup在如下兩種情況下會判斷是否要攔截當前事件:事件型別為ACTION DOWN或者mFirstTouchTarget != null,ACTION_ DOWN事件好理解,那么mFirstTouchTarget!=null是什么意思呢?這個從后面的代碼邏輯可以看出來,當事件由ViewGroup的子元素成功處理時,mFirstTouchTarget 會被賦值并指向子
元素,換種方式來說,當ViewGroup 不攔截事件并將事件交由子元素處理時
mFirstTouchTarget != null, 反過來,一旦事件由當前ViewGroup 攔截時,mFirstTouchTarget != null就不成立,那么當ACTION_ MOVE和ACTION UP事件到來
時,由于(actionMasked == MotionEvent. ACTION DOWN II mFirstTouchTarget != null)這
個條件為false, 將導致ViewGroup的onInterceptTouchEvent 不會再被呼叫,并且同一序列中的其他事件都會默認交給它處理,
分析二
當然,這里有一種特殊情況,那就是FLAG_ DISALLOW_ INTERCEPT 標記位,這個
標記位是通過requestDisallowInterceptTouchEvent 方法來設定的,一般用于子 View中, FLAG_ DISALLOW_ INTERCEPT 一旦設定后,ViewGroup 將無法攔截除了 ACTION_ DOWN以外的其他點擊事件,為什么說是除了ACTION_ DOWN以外的其他事 件呢?這是因為ViewGroup在分發事件時,如果是ACTION DOWN就會重置 FLAG_ DISALLOW_ INTERCEPT這個標記位,將導致子View中設定的這個標記位無效, 因此,當面對ACTION_DOWN事件時,ViewGroup總是會呼叫自己的onInterceptTouchEvent方法來詢問自己是否要攔截事件,這一點從原始碼中也可以看出來,
分析三
如果ViewGroup在這里對事件進行了ACTION_DOWN攔截,則會呼叫 super.dispatchTouchEvent(event)對事 件進行處理,因為ViewGroup的父類為View,在View的dispatchTouchEvent()中會呼叫onTouchEvent(),即ViewGroup在選擇對事件進行攔截后會交給ViewGroup的onTouchEvent去處理,
分析四
ViewGroup消費了ACTION_DOWN后,對后續事件的處理
分析過ViewGroup的dispatchTouchEvent()發現,當ViewGroup對ACTION_DOWN事件攔截后, mFirstTouchTarget 的值應該還是空的,這就使得ViewGroup的(dispatchTouchEventactionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)不成立 走else,使得intercepted = false;這樣的話 ,ViewGroup在這里對事件進行了ACTION_DOWN攔截之后的,對后續的ACTION_MOVE 和ACTION_UP都進行攔截,流程與ViewGroup對事件ACTION_DOWN處理的流程一致,
1.2
class ViewGroup:
public boolean dispatchTouchEvent(MotionEvent ev) {
....
final View[] children = mChildren;
//對子View進行遍歷
//我們看到了for回圈,首先遍歷ViewGroup的子元素,判斷子元素是否能夠接收到點
//擊事件,如果子元素能夠接收到點擊事件,則交由子元素來處理,需要注意這個for回圈 //是倒序遍歷的,即從最上層的子View開始往內層遍歷,
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 注釋1 具體分析在下文
//判斷1,View可見并且沒有播放影片,2,點擊事件的坐標落在View的范圍內
//如果上述兩個條件有一項不滿足則continue繼續回圈下一個View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 注釋 2 具體分析見下文
newTouchTarget = getTouchTarget(child);
//如果有子View處理即newTouchTarget 不為null則跳出回圈,
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent第三個引數child這里不為null
//實際呼叫的是child的dispatchTouchEvent方法
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();
//當child處理了點擊事件,那么會設定mFirstTouchTarget 在addTouchTarget被賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View處理了事件,然后就跳出了for回圈
break;
}
}
}
分析1
在注釋1處,有這樣的判斷 if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null))
判斷1,View可見并且沒有播放影片,2,點擊事件的坐標落在View的范圍內
若該判斷不生效即 不可見 沒播放影片, 或者 坐標點不在該View里面,則執行continue跳過這次回圈,
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
// 判斷有沒有可見并且沒有播放影片的函式
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
* @hide
*/
// 判斷坐標是否落在了 該View里面
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
//呼叫View的pointInView方法進行判斷坐標點是否在View內
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
分析2
在1.2代碼里 對view可見性 坐標是否落在該View等條件判斷完后,會有這樣的步驟
newTouchTarget = getTouchTarget(child),此時由于ACTION_DOWN事件還未被該ViewGroup的任何一個子View處理,結合上面的分析此時mFirstTouchTarget為null,所以此時getTouchTarget的回傳值為null
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
分析3
因為getTouchTarget()在ViewGroup的回傳值為null,最有接下來進入到 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) ,把之前的代碼copy下來方便分析,dispatchTransformedTouchEvent這和函式將會呼叫ViewGroup中子View的dispatchTouchEvent,并把結果回傳回來,如果子View的onTouchEven()消費了事件,那么
dispatchTransformedTouchEvent的回傳值為Ture,先回在注釋1處呼叫newTouchTarget = addTouchTarget(child, idBitsToAssign),這一步會把消費掉ACTION_DOWN的子View記錄下來,同時使得mFirstTouchTarget 不再為null,然后在注釋2處break ,結束ViewGroup中對子View的遍歷
class ViewGroup:
public boolean dispatchTouchEvent(MotionEvent ev) {
.....
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();
//當child處理了點擊事件,那么會設定mFirstTouchTarget 在addTouchTarget被賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign); // 注釋1
alreadyDispatchedToNewTouchTarget = true;
//子View處理了事件,然后就跳出了for回圈
break; // 注釋2
}
// 通過這函式可以看出,這里采用了頭插法把target 插入到一個單向鏈表中,
//其中TouchTarget.obtain是產生一個TouchTarget 物件,TouchTarget存有消費事件的View
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
TouchTarget.java
// 觀察TouchTarget的欄位可以發現嗎,TouchTarget里面存有消費事件的View,
//pointerIdBits(和多點觸碰有關),還有指向下一個節點的參考
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// The touched child view.
@UnsupportedAppUsage
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// The next target in the target list.
public TouchTarget next;
分析4
ViewGroup的子View消費了ACTION_DOWN事件后,ViewGroup對后續事件的處理
這里的處理邏輯在代碼里的注釋了做了詳細的說明,這里就不再贅述了,
class ViewGroup:
public boolean dispatchTouchEvent(MotionEvent ev) {
.....
// Dispatch to touch targets.
// 由之前的分析可知,當ViewGroup中的一子View消費了事件后,mFirstTouchTarget
//不在為空,故將執行else
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// mFirstTouchTarget != null會走到這里
// 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) {
//注釋1 在這里會不斷從target鏈表里在取出target物件,(在單點觸摸的時候
//只有一個物件),
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 注釋2 在這里呼叫了dispatchTransformedTouchEvent 引數中
// target.child傳了進去,target.child是消費ACTION_DOWN事件
// 的View ,dispatchTransformedTouchEvent 將會呼叫該View的
// dispatchTouchEvent去處理事件,
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
// 對target做回收,實作復用
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
}
return handled;
}
......
2. View中的分發
2.1
在View的dispatchTouchEvent有以下流程:
class View:
public boolean dispatchTouchEvent(MotionEvent ev) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
// //如果視窗沒有被遮蓋 注釋1 具體分析見下文
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
下面的代碼是對上文代碼注釋1處的截取
從下下面代碼的注釋1可以看得出上面代碼我們可以看到View會先判斷是否設定了OnTouchListener,如果設定了OnTouchListener并且onTouch方法回傳了true,那么onTouchEvent不會被呼叫,當沒有設定OnTouchListener或者設定了OnTouchListener但是onTouch方法回傳false則會呼叫View自己的onTouchEvent方法,
//如果視窗沒有被遮蓋
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//當前監聽事件
ListenerInfo li = mListenerInfo;
//需要特別注意這個判斷當中的li.mOnTouchListener.onTouch(this, event)條件 //注釋1
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result為false呼叫自己的onTouchEvent方法處理
if (!result && onTouchEvent(event)) {
result = true;
}
}
2.2
接下來看onTouchEvent方法,
class View:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//1.如果View是設定成不可用的(DISABLED)仍然會消費點擊事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
...
//2.CLICKABLE 和LONG_CLICKABLE只要有一個為true就消費這個事件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//3.在ACTION_UP方法發生時會觸發performClick()方法
performClick();
}
}
}
...
break;
}
...
return true;
}
return false;
}
2.3
View事件方法執行順序
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
上文對onTouchListener 和onTouchEvent 的處理做了簡單的分析,接下來分析 onLongClickListener 和 onClickListener
onLongClickListener
長按事件可以分解為 按下 和 抬起 兩個步驟,先在ACTION_DOWN事件里找找,具體分析見注釋,
public boolean onTouchEvent(MotionEvent event) {
...
case MotionEvent.ACTION_DOWN:
...
//mHasPerformedLongPress用于標記是否已經長按
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
// ViewConfiguration.getLongPressTimeout 方法可以獲取到長按觸發所需的時間,默認是500ms,
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
...
}
接下來分析checkForLongClick方法
完成了相關的設定后把 mPendingCheckForLongPress延遲一段時間(delay)再發送出去,這里使用了Handel(改日寫一篇handler的文章), postDelayed(mPendingCheckForLongPress, delay)的第一個引數是一定延時后執行的任務,第二個引數是延時,
private void checkForLongClick(long delay, float x, float y, int classification) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
mPendingCheckForLongPress.setClassification(classification);
//1
postDelayed(mPendingCheckForLongPress, delay);
}
}
CheckForLongPress 類實作了run方法,那么在訊息發出的500毫秒后將會執行run該run方法
private final class CheckForLongPress implements Runnable {
...
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
recordGestureClassification(mClassification);
//1
if (performLongClick(mX, mY)) { //注意這里
//2
mHasPerformedLongPress = true;
}
}
}
...
}
觀察代碼發現,在onLongClickListener 發送的延時訊息中,在回呼onLongClick()方法之前的一系列呼叫中都沒有進行判斷,也就是說只要這個callback沒有被移除,在指定時間之后肯定要回呼onLongClick()方法,所以說有這種狀況當我么長按了490ms時還未達到500ms,這時候手指抬分發ACTION_UP事件,同時移除hander機制中訊息佇列中onLongClickListener 發送的延時訊息,那么當時間到達500ms時onLongClickListener 并不會得到執行,
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
//1
handled = li.mOnLongClickListener.onLongClick(View.this);
}
...
return handled;
}
查看View 的onTouchEvent方法中case : ACTION_UP的情況: 確實有點擊時發送的延時訊息進行了移除,
若沒有進行移除,表明點擊的時間大于了500ms則會先檢查有沒有長按事件的監聽,再執行onLongClick,
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback(); //移除長按的callback
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
onClickListener
在View的onTouchEvent中的case ACTION_UP中有對onClickListener的處理,具體分析見原始碼中的注釋
View.java
public boolean onTouchEvent(MotionEvent event) {
...
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
//在這里可以看到new 出了一個PerformClick,在后面的中又通過post將他post到handler
//中,所以想都不用想 PerformClick繼承自Runable 實作了run方法
mPerformClick = new PerformClick();
}
// 在這里通過無延遲的post的方法將PerformClick psot到主執行緒處理
if (!post(mPerformClick)) {
performClick();
}
}
}
...
break;
...
進入到 PerformClick類中瞧瞧
private final class PerformClick implements Runnable {
@Override
public void run() {
recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
performClickInternal(); //在run方法里面執行了 performClickInternal
}
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
//performClickInternal中又呼叫了 performClick
return performClick();
}
在performClick中我們可以清晰的看到對OnClickListener 進行了處理
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 在這里呼叫了onClick事件
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
回到在View的onTouchEvent中的case ACTION_UP的原始碼(如下):細心的人可能已經發現了,通過上面的 在 注意這里 1 post訊息 中最后還是呼叫 performClick來呼叫onClick的處理,在 注意這里 2 中當 post不成功時,直接呼叫performClick 對消進行處理,那么為什不能在上面直接呼叫performClick進行處理,非要大費周章的使用handler機制來處理呢??哈哈在上面的英文注釋中已經說明了很清楚了:Use a Runnable and post this rather than calling
performClick directly. This lets other visual stateof the view update before click actions start.說白了 就是怕這個performClick處理的 太久影響了view視圖的更新,
View.java
public boolean onTouchEvent(MotionEvent event) {
...
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
//在這里可以看到new 出了一個PerformClick,在后面的中又通過post將他post到handler
//中,所以想都不用想 PerformClick繼承自Runable 實作了run方法
mPerformClick = new PerformClick();
}
// 在這里通過無延遲的post的方法將PerformClick psot到主執行緒處理
if (!post(mPerformClick)) { 注意這里 1
performClick(); 注意這里 2
}
}
}
...
break;
...
簡單總結一下 onLongClickListener 和 onClickListener的處理程序:當ACTION_DOWN到達一個view后,他會想通過handler post一個500ms訊息(我稱之為長按訊息)給主執行緒,這個訊息里面封裝著LongClickListener的處理任務,如果在500ms之內收到了ACTION_UP事件,則首先會移除掉主執行緒訊息佇列中的長按訊息,然后去執行onClickListener對Click的執行邏輯,因為主執行緒中長按訊息已經被移除,所以500ms后不會執行LongClick的任務,
結語
android的事件分發流程分析到這里就結束了,因為時間和我自己技識訓是很淺薄的原因,這篇文章里難免會有些錯誤的地方,我歡迎大家給我指出來,我們共同進步
同時寫這個博客的時候參考很多優秀的博客和書籍,在這里向各位作者表示感謝,我也只是站在前人的肩膀上,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/402769.html
標籤:其他
