View的事件分發(fā)所針對(duì)的是MotionEvent事件叼丑,在Touch過(guò)程中會(huì)產(chǎn)生大量的MotionEvent欣范,記錄了與Touch相關(guān)的事件掠河。一次ACTION_DOWN栈虚、中間可能多次ACTION_MOVE、一次ACTION_UP汁掠,這便是一次完整的MotionEvent事件略吨。在我們點(diǎn)擊屏幕的那一刻,會(huì)先經(jīng)過(guò)硬件的一系列處理考阱,然后在當(dāng)前應(yīng)用的主(UI)線程中接收到來(lái)自底層傳輸過(guò)來(lái)的input事件翠忠,將事件交付于ViewRootImpl的enqueueInputEvent()方法,通過(guò)ViewRootImpl的內(nèi)部類InputStage轉(zhuǎn)換處理乞榨,最終交給View的dispatchTouchEvent()方法秽之,事件分發(fā)的開(kāi)始。
一個(gè)應(yīng)用程序的根視圖(頂級(jí)View)是DecorView吃既,也就是說(shuō)考榨,View的事件分發(fā)實(shí)際是由DecorView中的dispatchTouchEvent()方法開(kāi)始的。
在處理View的事件分發(fā)時(shí),View和ViewGroup(繼承自View)稍有差異。
- View的相關(guān)處理方法:
dispatchTouchEvent()糯钙、onTouchEvent()- ViewGroup的相關(guān)處理方法:
dispatchTouchEvent()荔泳、onInterceptTouchEvent()、onTouchEvent()
1护奈、DecorView # dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
上面代碼中单鹿,mWindow是一個(gè)PhoneWindow類型的變量园匹,在Activity的attach()方法中對(duì)mWindow進(jìn)行賦值【可以參考這篇文章 — Activity的啟動(dòng)過(guò)程】乐尊。且Activity實(shí)現(xiàn)了Window.Callback接口(實(shí)現(xiàn)Window.Callback的不只有Activity戚丸,比如Dialog,這里以Activity為例)扔嵌,也就是說(shuō):cb.dispatchTouchEvent()回調(diào)的是Activity中的dispatchTouchEvent()昏滴,執(zhí)行這一步首先要滿足前提條cb是否存在,PhoneWindow沒(méi)有銷(xiāo)毀对人,mFeatureId < 0表示DecorView存在。否則會(huì)執(zhí)行ViewGroup的dispatchTouchEvent()方法(DecorView繼承FrameLayout)拂共。
2牺弄、Activity # dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//內(nèi)部是一個(gè)空實(shí)現(xiàn),用于點(diǎn)擊時(shí)與用戶交互
onUserInteraction();
}
//getWindow()返回PhoneWindow對(duì)象
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//若前面沒(méi)有消費(fèi)事件宜狐,最后交給Activity的onTouchEvent
return onTouchEvent(ev);
}
這里的實(shí)現(xiàn)很簡(jiǎn)潔势告,先交給PhoneWindow分發(fā),如果沒(méi)有消費(fèi)事件抚恒,則在Activity的onTouchEvent()方法中消費(fèi)咱台。
3、PhoneWindow # superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor指的是DecorView
return mDecor.superDispatchTouchEvent(event);
}
進(jìn)入DecorView中的superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
前面已注明了DecorView是繼承自FrameLayout(繼承ViewGroup)俭驮,那么這里也就自然而然的把分發(fā)任務(wù)交給了ViewGroup的dispatchTouchEvent()回溺。
4、ViewGroup # dispatchTouchEvent()混萝,由于代碼量太長(zhǎng)遗遵,這里選擇關(guān)鍵性的代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
... ...
// 如果是點(diǎn)擊事件,先對(duì)之前的狀態(tài)進(jìn)行清除
//1逸嘀、把mFirstTouchTarget(TouchTarget對(duì)象)鏈表清空车要,同時(shí)mFirstTouchTarget置空
//mFirstTouchTarget鏈表內(nèi)部存放的是接受了觸摸事件的view
//2、重置FLAG_DISALLOW_INTERCEPT標(biāo)識(shí)位(若設(shè)置了這個(gè)標(biāo)識(shí)崭倘,表示禁止ViewGroup的攔截)
//可以通過(guò)requestDisallowInterceptTouchEvent()進(jìn)行設(shè)置翼岁,
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 檢查是否進(jìn)行攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 檢查是否已設(shè)置FLAG_DISALLOW_INTERCEPT標(biāo)識(shí)
// 若設(shè)置,則不進(jìn)行攔截司光,intercepted為false
// 否則攔截琅坡,由onInterceptTouchEvent()決定intercepted的狀態(tài)
// onInterceptTouchEvent()默認(rèn)返回false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
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.
intercepted = true;
}
... ...
// 檢查當(dāng)前觸摸事件是否被取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//把當(dāng)前ViewGrop的觸摸事件進(jìn)行分發(fā)給子View和子ViewGroup
//在這之前需要滿足兩個(gè)條件:1、觸摸事件沒(méi)有取消 2飘庄、沒(méi)有被攔截
//如果當(dāng)前ViewGroup的子View接收到觸摸事件脑蠕,則把該子View添加到mFirstTouchTarget鏈表
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//記錄觸摸事件的序列號(hào),事件Id,點(diǎn)擊事件的Id總是為0谴仙,
//在多指觸控下迂求,會(huì)產(chǎn)生多個(gè)Id,比如第一根手指,記錄0晃跺,第二根手機(jī)記錄1 ~ ~ ~
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// 清除早期的觸摸目標(biāo)
removePointersFromTouchTargets(idBitsToAssign);
// 獲取當(dāng)前ViewGroup包含的子元素揩局,進(jìn)行遍歷,然后對(duì)觸摸事件進(jìn)行分發(fā)
// 如果子元素也是ViewGroup掀虎,那么就對(duì)其里面的子元素遍歷凌盯,如此遞歸下去
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;
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;
}
//判斷子View是否能接收觸摸事件
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 先查找mFirstTouchTarget鏈表,是否存在該View
// 若查找到烹玉,則返回當(dāng)前View在鏈表中的節(jié)點(diǎn)賦值給newTouchTarget
// 若沒(méi)有驰怎,則返回null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//重置子View的mPrivateFlags中的PFLAG_CANCEL_NEXT_UP_EVENT
resetCancelNextUpFlag(child);
//將觸摸事件分發(fā)給子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;
}
//若子元素(包含子View和子ViewGroup)可以接收到觸摸事件,
// 通過(guò)addTouchTarget()把已接收觸摸事件的子元素添加到
//mFirstTouchTarget鏈表二打,并把當(dāng)前子元素作為頭結(jié)點(diǎn)返回县忌,賦值給newTouchTarget
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
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();
}
//在newTouchTarget為null,mFirstTouchTarget不為null時(shí)继效,
//把mFirstTouchTarget賦值給newTouchTarget症杏,作為鏈表第一個(gè)節(jié)點(diǎn)
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;
}
}
}
... ...
// 進(jìn)一步對(duì)觸摸事件的分發(fā)進(jìn)行處理,mFirstTouchTarget==null表示未有
// 子View接收到觸摸事件瑞信,此時(shí)厉颤,會(huì)交由ViewGroup的父類View的dispatchTouchEvent()進(jìn)行分發(fā),
//然后再交給onTouch()或onTouchEvent()進(jìn)行處理
//onTouch()優(yōu)先于onTouchEvent()凡简,但是要setOnTouchListener()后才生效
// 如果mFirstTouchTarget != null逼友,表示存在子View,則分發(fā)到子View
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} 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) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 完成后重置觸摸狀態(tài)
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;
}
關(guān)于ViewGroup的事件分發(fā)秤涩,由于代碼太長(zhǎng)翁逞,所以直接在里面注釋了,這樣跟著代碼會(huì)比較容易理解溉仑。上面闡述了關(guān)于ViewGroup的整個(gè)分發(fā)過(guò)程挖函,在第二個(gè)省略號(hào)的下面一段代碼,從其 if 條件可以知道浊竟,這段代碼只有在ViewGroup發(fā)生ACTION_DOWN事件時(shí)才會(huì)執(zhí)行(分發(fā)事件)怨喘,而后續(xù)的事件(ACTION_MOVE、ACTION_UP)將由第三個(gè)省略號(hào)下面一段代碼中執(zhí)行分發(fā)振定,這時(shí)會(huì)遍歷mFirstTouchTarget鏈表必怜,找到具體的子View進(jìn)行分配事件。(實(shí)際就是:假設(shè)當(dāng)前ViewGroup中包含三個(gè)子View后频,分別是A B C梳庆,如果ACTION_DOWN事件分發(fā)到了A暖途,那么后續(xù)的事件一定不會(huì)分發(fā)到B或C)
下面進(jìn)一步去分析dispatchTransformedTouchEvent()
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//這里檢測(cè)是否需要發(fā)送ACTION_CANCEL事件。
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//分發(fā)給當(dāng)前ViewGroup
handled = super.dispatchTouchEvent(event);
} else {
//分發(fā)給子View
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 計(jì)算觸摸事件Id
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
//若觸摸事件Id相同膏执,不需要重新計(jì)算MotionEvent驻售,直接進(jìn)行分發(fā)
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
//child為null,交由父類分發(fā)更米,否則交由child分發(fā)
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
在dispatchTransformedTouchEvent()中主要是對(duì)子View進(jìn)行判斷欺栗,如果子View為null,則分發(fā)交由super.dispatchTouchEvent()征峦,也就是由View的dispatchTouchEvent()分發(fā)迟几,然后交給onTouch()(如果設(shè)置了OnTouchListener)或onTouchEvent(),如果這時(shí)候onTouchEvent()依舊返回false栏笆,則交由當(dāng)前ViewGroup的上一級(jí)去處理类腮。
5、View # dispatchTouchEvent()蛉加,省略了部分代碼
public boolean dispatchTouchEvent(MotionEvent event) {
... ...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 接收到ACTION_DOWN存哲,停止嵌套滑動(dòng)
stopNestedScroll();
}
//判斷View是否被屏蔽,是否能被點(diǎn)擊七婴,
//然后再確定是否執(zhí)行onTouch或onTouchEvent
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//在這里注意下li.mOnTouchListener.onTouch(),這里說(shuō)明了
//onTouch會(huì)先于onTouchEvent執(zhí)行察滑,前提:當(dāng)前View設(shè)置了OnTouchListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//若沒(méi)有設(shè)置OnTouchListener打厘,交給onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
... ...
return result;
}
關(guān)鍵的步驟已在代碼中注釋,就不再另外贅述了贺辰。
接著看下View的onTouchEvent()方法户盯。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//計(jì)算View是否可點(diǎn)擊
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//這里View被禁用,調(diào)用了setEnabled(false)或android:enabled=false
//返回其點(diǎn)擊狀態(tài)饲化,View默認(rèn)是不可點(diǎn)擊的
//可以通過(guò)setClickable()或者android:clickable設(shè)置點(diǎn)擊狀態(tài)
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
//委托事件給其它View莽鸭,mTouchDelegate默認(rèn)為null
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//判斷View的點(diǎn)擊狀態(tài),設(shè)置焦點(diǎn)吃靠。后續(xù)執(zhí)行onClick()硫眨、onLongClick()
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) {
// 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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
關(guān)于View的事件分發(fā)大概流程到這里就結(jié)束了。
Activity > ViewGroup(可包含多個(gè)ViewGroup) > View 巢块。
最后通過(guò)一張圖的形式整理下上面的思路: