前提條件源碼是基于android 26分析的
基礎知識
什么是觸摸事件
觸摸事件,是 Android 用來描述你的手對屏幕做的事情的最小單元。關鍵詞有兩個:手勢(你的手對屏幕做的事情)、最小單元。
手勢:按下、移動、抬起、點擊事件、長按事件、滑動事件。
最小單元:不可在拆分,比如按下(down)、移動(move)、抬起(up)就是不可再拆分的。而點擊事件、長按事件、滑動事件則是由那些不可再拆分的事件組合起來的。比如點擊事件是由按下、抬起組成的。長按事件是按下并保持一段時間不動。注意一下滑動事件和移動(move)的區(qū)別,滑動事件可以理解為是由若干個“move”組合起來的,系統(tǒng)對觸摸的捕捉是很靈敏的,手指放在屏幕上稍微抖一下,都會產(chǎn)生“move”事件。
這里還有一個取消(cancel)事件,暫時不予理會
MotionEvent 對象
MotionEvent 對象就是對觸摸事件相關信息的封裝
事件類型(包括但不局限于):ACTION_DOWN(按下)、ACTION_ MOVE(移動)、ACTION_ UP(抬起)。
坐標系
坐標還分參考系,有以屏幕作為參考系的坐標值,也有以被觸摸的 View 作為參考系的坐標值。
MotionEvent 的對象內(nèi)的方法與坐標系是存在一定關聯(lián)的具體的可參考下面的圖
一個完整的事件序列
觸摸事件與一個完整的事件序列的區(qū)別
-
觸摸事件
最小單元,任何一個對屏幕的操作都是由多個觸摸事件構成的,觸摸事件分別有按下、移動、抬起、取消等 -
一個完整的事件序列
ACTION_DOWN(一個)–>ACTION_MOVE(數(shù)量不定)–>ACTION_UP(一個),從 ACTION_DOWN 到 ACTION_UP,稱為一個完整的事件序列,即某一次手指按下到手指抬起算是一個完整的事件序列,下一次手指按下到抬起是另一個完整的事件序列。
筆者沒有真的考究過 Android 官方是不是真的有“事件序列”這么一個概念性的名詞,只是為了分析源碼方便理解才這樣說的,這也是android“擬人化”行為的一種標識吧,因為從ViewGroup 的源碼里面,確實是可以找到在遇到 ACTION_DOWN 時清除某些標記位(以避免受到前一個事件序列的影響)
為什么要事件分發(fā)
簡單地講,事件分發(fā)就是為了解決“誰來干活”的問題。當一個事件發(fā)生,有超過一個View(或者 ViewGroup)能夠?qū)λM行響應(處理)時,就要確定到底誰來處理這個事件。
View的事件分發(fā)
View的事件分發(fā),是指 View.java 源碼里對觸摸事件進行傳遞的流程
流程圖
View 事件分發(fā)的流程圖是一個主干流程,去掉一些細節(jié)的流程
流程圖如下
源碼
public boolean dispatchTouchEvent(MotionEvent event) {
....//省略無關的代碼
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;
}
}
....
return result;
}
可以看到關鍵代碼那里的條件就是流程圖上面的條件
我們再來看onTouchEvent的源碼,這里就是平常設置點擊事件的回調(diào)地方
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
....//省略無關代碼
//如果view的clickable屬性設置為false也不會響應點擊事件
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
....//省略無關代碼
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();
}
//這里就是處理點擊按鈕的時間回調(diào)
if (!post(mPerformClick)) {
performClick();
}
}
}
....//省略無關代碼
}
mIgnoreNextUpEvent = false;
break;
return true;//返回這個的話就說明事件已經(jīng)消耗了
}
return false;//返回這個的話就說明事件拋回給它的父類處理
}
流程說明
有可能處理事件的有兩個地方,一個是外部設置的 OnTouchListener 監(jiān)聽器,即OnTouchListener的onTouch(),一個是 View 內(nèi)部的處理方法,即 onTouchEvent()。而且外部設置的監(jiān)聽器優(yōu)先獲取事件。
當外部設置的監(jiān)聽器處理了事件(即有設置監(jiān)聽器、View 處于 ENABLE 狀態(tài)、監(jiān)聽器在onTouch()里面返回 true 三者均成立),dispatchTouchEvent() 返回 true,當前 View的 onTouchEvent() 不會觸發(fā)。
如果外部的監(jiān)聽器不處理事件,則dispatchTouchEvent() 的返回值由 View 自己的onTouchEvent()決定。
注意一下,對于上級 ViewGroup(也就是當前 View 的父容器)而言,它只認識子 View的dispatchTouchEvent()方法,不認識另外兩個處理事件的方法。子View的OnTouchListener 和 onTouchEvent() 都是在自己的 dispatchTouchEvent() 里面調(diào)用的,他們兩個會影響 dispatchTouchEvent() 的返回值,但是對于上級 ViewGroup 而言,它只認識 dispatchTouchEvent() 的返回值就足夠了。
ViewGroup 的事件分發(fā)
流程圖
流程如下圖所示:
源碼
public boolean dispatchTouchEvent(MotionEvent ev) {
....//省略無關代碼
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
/**
* 第一步:對于ACTION_DOWN進行處理(Handle an initial down)
* 因為ACTION_DOWN是一系列事件的開端,當是ACTION_DOWN時進行一些初始化操作.
* 從源碼的注釋也可以看出來:清除以往的Touch狀態(tài)(state)開始新的手勢(gesture)
* cancelAndClearTouchTargets(ev)中有一個非常重要的操作:
* 將mFirstTouchTarget設置為null!!!!
* 隨后在resetTouchState()中重置Touch狀態(tài)標識
*/
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
....//省略無關代碼
// Check for interception.
// 使用變量intercepted來標記ViewGroup是否攔截Touch事件的傳遞.
final boolean intercepted;
//ACTION_DOWN事件或者已經(jīng)找到了事件分發(fā)的目標對象,在一個事件序列是非ACTION_DOWN事件都會進入這里
//對應流程圖中的第一個判斷分支
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//設置禁止攔截的標記 1.ACTION_DOWN事件與非ACTION_DOWN事件都會走這里
//因為在其他地方可能調(diào)用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)來影響這個變量的值,
//所以在一個事件序列中如果多個事件之后確定了是由誰來處理事件,就在這個事件序列中滿足一定條件可以設置這個值
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//再次詢問是否會攔截 1.ACTION_DOWN事件與非ACTION_DOWN事件都會走這里
//常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//當禁止攔截判斷時(即disallowIntercept為true)設置intercepted = false
intercepted = false;
}
} else {
//當事件不是ACTION_DOWN并且mFirstTouchTarget為null(即沒有Touch的目標組件)時
//設置 intercepted = true表示ViewGroup執(zhí)行Touch事件攔截的操作。
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
....//省略無關代碼
//不是ACTION_CANCEL并且ViewGroup的攔截標志位intercepted為false(不攔截)
if (!canceled && !intercepted) {
....//省略無關代碼
//處理ACTION_DOWN事件.這個環(huán)節(jié)比較繁瑣.
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍歷viewGroup下所有的子view找到可以消費事件的子view,并且把事件消費的目標賦值
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
....//省略無關代碼
//這個條件是第二次發(fā)生點擊事件(第二個事件序列),這個快速找到事件消費的目標
newTouchTarget = getTouchTarget(child);
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;
}
/**
* 如果上面的if不滿足,當然也不會執(zhí)行break語句.
* 于是代碼會執(zhí)行到這里來.
*
* 調(diào)用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做
* 遞歸處理(也就是遍歷該子View的View樹)
* 該方法很重要,看一下源碼中關于該方法的描述:
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
* 將Touch事件傳遞給特定的子View.
* 該方法十分重要!!!!在該方法中為一個遞歸調(diào)用,會遞歸調(diào)用dispatchTouchEvent()方法?。。。。。。。。。。。。?!
* 在dispatchTouchEvent()中:
* 如果子View為ViewGroup并且Touch沒有被攔截那么遞歸調(diào)用dispatchTouchEvent()
* 如果子View為View那么就會調(diào)用其onTouchEvent(),這個就不再贅述了.
*
*
* 該方法返回true則表示子View消費掉該事件,同時進入該if判斷.
* 滿足if語句后重要的操作有:
* 1 給newTouchTarget賦值
* 2 給alreadyDispatchedToNewTouchTarget賦值為true.
* 看這個比較長的英語名字也可知其含義:已經(jīng)將Touch派發(fā)給新的TouchTarget
* 3 執(zhí)行break.
* 因為該for循環(huán)遍歷子View判斷哪個子View接受Touch事件,既然已經(jīng)找到了
* 那么就跳出該for循環(huán).
* 4 注意:
* 如果dispatchTransformedTouchEvent()返回false即子View
* 的onTouchEvent返回false(即Touch事件未被消費)那么就不滿足該if條件,也就無法執(zhí)行addTouchTarget()
* 從而導致mFirstTouchTarget為null.那么該子View就無法繼續(xù)處理ACTION_MOVE事件
* 和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!!
* 5 注意:
* 如果dispatchTransformedTouchEvent()返回true即子View
* 的onTouchEvent返回true(即Touch事件被消費)那么就滿足該if條件.
* 從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!!
* 6 小結:
* 對于此處ACTION_DOWN的處理具體體現(xiàn)在dispatchTransformedTouchEvent()
* 該方法返回boolean,如下:
* true---->事件被消費----->mFirstTouchTarget!=null
* false--->事件未被消費---->mFirstTouchTarget==null
* 因為在dispatchTransformedTouchEvent()會調(diào)用遞歸調(diào)用dispatchTouchEvent()和onTouchEvent()
* 所以dispatchTransformedTouchEvent()的返回值實際上是由onTouchEvent()決定的.
* 簡單地說onTouchEvent()是否消費了Touch事件(true or false)的返回值決定了dispatchTransformedTouchEvent()
* 的返回值!!!!!!!!!!!!!從而決定了mFirstTouchTarget是否為null!!!!!!!!!!!!!!!!從而進一步?jīng)Q定了ViewGroup是否
* 處理Touch事件.這一點在下面的代碼中很有體現(xiàn).
*
*
*/
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;
}
// 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();
}
//該條件表示沒有找到子view接收事件并且之前的mFirstTouchTarget不為空
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指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
}
/**
* 分發(fā)Touch事件至target
*
* 經(jīng)過上面對于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況:
* 1 mFirstTouchTarget為null
* 2 mFirstTouchTarget不為null
*
* 當然如果不是ACTION_DOWN就不會經(jīng)過上面較繁瑣的流程
* 而是從此處開始執(zhí)行,比如ACTION_MOVE和ACTION_UP
*/
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//簡要分析
//1.子view不處理事件會執(zhí)行這里
//2.無法找到事件消費的目標
//都是走super.dispatchTouchEvent(event);
/**
* 詳細分析
* 情況1:mFirstTouchTarget為null
*
* 經(jīng)過上面的分析mFirstTouchTarget為null就是說Touch事件未被消費.
* 即沒有找到能夠消費touch事件的子組件或Touch事件被攔截了,
* 則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
* 即子View沒有消費Touch事件,那么子View的上層ViewGroup才會調(diào)用其onTouchEvent()處理Touch事件.
* 在源碼中的注釋為:No touch targets so treat this as an ordinary view.
* 也就是說此時ViewGroup像一個普通的View那樣調(diào)用dispatchTouchEvent(),且在dispatchTouchEvent()
* 中會去調(diào)用onTouchEvent()方法.
* 具體的說就是在調(diào)用dispatchTransformedTouchEvent()時第三個參數(shù)為null.
* 第三個參數(shù)View child為null會做什么樣的處理呢?
* 請參見下面dispatchTransformedTouchEvent()的源碼分析
*
*
*/
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/**
* 情況2:mFirstTouchTarget不為null即找到了可以消費Touch事件的子View 多數(shù)情況下后續(xù)Touch事件可以傳遞到該子View,
* 但是在事件還沒確定是viewGroup還是view處理時會一直流經(jīng)兩個的事件流中
*/
// 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) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//對于非ACTION_DOWN事件繼續(xù)傳遞給目標子組件進行處理,依然是遞歸調(diào)用dispatchTransformedTouchEvent()
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//對于非ACTION_DOWN事件如果中途攔截了(intercepted = true),這個條件會把mFirstTouchTarget置為null
//等下一個touch事件就會把事件給viewGroup自己處理
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
/**
* 處理ACTION_UP和ACTION_CANCEL
* Update list of touch targets for pointer up or cancel, if needed.
* 在此主要的操作是還原狀態(tài)
*/
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}