一個(gè)點(diǎn)擊或觸摸事件會(huì)被內(nèi)部封裝成MotionEvent對(duì)象截驮。而事件分發(fā)就是將MotionEvent往子View傳遞。有View的地方就有Window担神,View必須依附于Window進(jìn)行展示验靡。我們通過(guò)setContentView()設(shè)置的布局弯洗,會(huì)被添加到DecorView中能真,DecorView會(huì)被添加到Window,而Window則被加到Activity中,這里用一張圖展示它們的層次關(guān)系粉铐。
事件的分發(fā)會(huì)經(jīng)歷3個(gè)方法疼约,往下遞歸調(diào)用,分別是dispatchTouchEvent蝙泼、onInterceptTouchEvent和onTouchEvent方法程剥。
從子View的角度來(lái)說(shuō),dispatchTouchEvent可以理解為接收到事件的意思汤踏,也就是當(dāng)父View要把事件發(fā)給我時(shí)织鲸,會(huì)調(diào)用dispatchTouchEvent來(lái)告知我接收這個(gè)事件,所以Activity溪胶、ViewGroup和View都有這個(gè)方法搂擦。
onInterceptTouchEvent是把事件攔截了的意思,Activity是下發(fā)的源頭哗脖,不能還沒(méi)發(fā)出去就把事件攔截掉瀑踢,而View不是容器,是沒(méi)有子View的才避,只有消費(fèi)和不消費(fèi)事件一說(shuō)橱夭,沒(méi)有必要攔截,所以桑逝,Activity和View都不會(huì)有onInterceptTouchEvent這個(gè)方法棘劣。
最后一個(gè)onTouchEvent是用來(lái)決定是否要消費(fèi)事件的,當(dāng)然消費(fèi)事件不止它一個(gè)楞遏,它的優(yōu)先級(jí)也不是最高的茬暇,還有設(shè)置觸摸監(jiān)聽的setOnTouchListener和點(diǎn)擊事件onClick方法。
這里假設(shè)一個(gè)最簡(jiǎn)單的場(chǎng)景橱健,ContentView里有一個(gè)ViewGroup而钞,ViewGroup里放了一個(gè)View。下面通過(guò)源碼一步步分析拘荡,但手指按下時(shí)臼节,Activity 會(huì)首先收到一個(gè)MotionEvent事件,它為ACTION_DOWN類型的珊皿。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
//事件分發(fā)源頭
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//通過(guò)Window下發(fā)
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//自己處理
return onTouchEvent(ev);
}
//空實(shí)現(xiàn)
public void onUserInteraction() {
}
}
Window是Activity的下一層网缝,而Activity接到事件后,并沒(méi)做什么處理蟋定,而是將事件交給Window去做分發(fā)粉臊。如果superDispatchTouchEvent返回了true,即說(shuō)明這個(gè)事件被某個(gè)控件消費(fèi)了驶兜,流程結(jié)束扼仲。如果返回false远寸,說(shuō)明這個(gè)事件沒(méi)有控件消費(fèi),則調(diào)用自己的onTouchEvent把事件消費(fèi)掉屠凶。
接下來(lái)看Window如何將事件傳遞給ViewGroup驰后。
public abstract class Window {
public abstract boolean superDispatchTouchEvent(MotionEvent event);
}
Window 是一個(gè)抽象類,superDispatchTouchEvent也是一個(gè)抽象方法矗愧,它的唯一實(shí)現(xiàn)類為PhoneWindow灶芝。
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
PhoneWindow也什么都沒(méi)做,將事件交給Window去做分發(fā)唉韭。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
前面的調(diào)用方法都是superDispatchTouchEvent夜涕,而不是dispatchTouchEvent,而這里我們第一次看到dispatchTouchEvent的調(diào)用属愤,也就是說(shuō)女器,從此處,事件開始向下一層層分發(fā)給子View了春塌。
而DecorView 繼承自FrameLayout 晓避,我們知道FrameLayout 是一個(gè)容器,即繼承了ViewGroup只壳,而且FrameLayout 也沒(méi)有重寫dispatchTouchEvent方法俏拱。
public class FrameLayout extends ViewGroup {
}
因此,事件將通過(guò)ViewGroup來(lái)處理吼句。上面提到過(guò)锅必,此時(shí)我們可以認(rèn)為,DecorView是第一個(gè)接到事件的ViewGroup惕艳。由于dispatchTouchEvent方法比較長(zhǎng)搞隐,我們一段段分析。
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
protected int mGroupFlags;
private TouchTarget mFirstTouchTarget;
//DecorView開始事件分發(fā)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//事件是否被處理了
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//每次ACTION_DOWN都重新開始分發(fā)的流程远搪,所以清理掉之前的事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//清除之前的所有事件
cancelAndClearTouchTargets(ev);
//重置
resetTouchState();
}
//檢查是否要攔截
final boolean intercepted;
//當(dāng)不是Down事件時(shí):
// 如果子View消費(fèi)了事件劣纲,mFirstTouchTarget不為null,
// mFirstTouchTarget為null,說(shuō)明子View都沒(méi)有消費(fèi)谁鳍,直接跳else執(zhí)行攔截
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
//是否攔截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//不攔截進(jìn)入判斷
if (!disallowIntercept) {
//如果重寫onInterceptTouchEvent返回true癞季,此處intercepted = true進(jìn)行攔截
intercepted = onInterceptTouchEvent(ev);//默認(rèn)為false
//恢復(fù)事件防止其改變
ev.setAction(action);
} else {
//不攔截
intercepted = false;
}
} else {
//子控件沒(méi)有消費(fèi)DOWN事件
intercepted = true;
}
這里有幾個(gè)關(guān)鍵的變量,intercepted用來(lái)控制事件是否要繼續(xù)往下分發(fā)倘潜,初始值為false绷柒,即不攔截。正常情況下涮因,當(dāng)按下手指時(shí)废睦,第一個(gè)事件會(huì)為ACTION_DOWN,即actionMasked是ACTION_DOWN养泡。mFirstTouchTarget 嗜湃,見名知意奈应,它表示第一個(gè)事件由誰(shuí)消費(fèi),但不包括自己购披,意思是钥组,如果有子控件消費(fèi)了down事件,那么mFirstTouchTarget 就會(huì)指向那個(gè)對(duì)象今瀑,不為null,如果沒(méi)有子控件消費(fèi)down事件点把,則為null橘荠。
那么當(dāng)用戶按下手指餅滑動(dòng)時(shí),第二個(gè)傳遞過(guò)來(lái)的事件則為ACTION_MOVE郎逃,actionMasked == MotionEvent.ACTION_DOWN不成立哥童,如果剛才的ACTION_DOWN事件子控件沒(méi)有消費(fèi),mFirstTouchTarget !=null也不成立褒翰,將進(jìn)入else判斷贮懈,intercepted 置為true。
也就是說(shuō)优训,子控件一開始沒(méi)有消費(fèi)ACTION_DOWN事件的話朵你,那則往后的事件都默認(rèn)攔截,不會(huì)往下發(fā)了揣非。同時(shí)也不會(huì)再執(zhí)行到onInterceptTouchEvent這個(gè)方法抡医。
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
//重置為0,即false
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
//請(qǐng)求父控件不攔截事件
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//如果子控件請(qǐng)求的和當(dāng)前狀態(tài)相同早敬,無(wú)需往下執(zhí)行
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
//是否攔截
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
//有父類繼續(xù)告知
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
FLAG_DISALLOW_INTERCEPT是一個(gè)標(biāo)志位忌傻,它可以通過(guò)requestDisallowInterceptTouchEvent來(lái)設(shè)置是否進(jìn)行攔截方法的調(diào)用,它一般用于子View來(lái)請(qǐng)求父控件攔截或不攔截事件搞监,但只能作用于非ACTION_DOWN的情況下水孩。因?yàn)槊看蜛CTION_DOWN都是一輪分發(fā)的重新開始,也就是說(shuō)之前的事件都廢棄了琐驴。resetTouchState會(huì)重置狀態(tài)俘种,包括FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)志位。
所以棍矛,接到ACTION_DOWN事件時(shí)安疗,總會(huì)執(zhí)行到onInterceptTouchEvent方法。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
這個(gè)方法很簡(jiǎn)單够委,如果子類不重寫的話默認(rèn)返回false荐类,不攔截。
//檢查事件是否取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//如果有必要茁帽,為DOWN事件檢查所有的目標(biāo)對(duì)象
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;//鏈表節(jié)點(diǎn)
boolean alreadyDispatchedToNewTouchTarget = false;
//事件沒(méi)有取消玉罐,同時(shí)沒(méi)有攔截屈嗤,則往下分發(fā)
if (!canceled && !intercepted) {
//如果事件為起始事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//newTouchTarget為null,并且有子View
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒序?qū)ふ沂欠裼凶覸iew處理此事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);
//如果子View有動(dòng)畫在執(zhí)行吊输,或者沒(méi)有落在這個(gè)View的范圍內(nèi)饶号,就繼續(xù)找下一個(gè)子View
if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//將接收事件的子View賦值給newTouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//1,如果子View是ViewGroup季蚂,會(huì)遞歸調(diào)用此dispatchTouchEvent往下分發(fā)茫船,如果返回某個(gè)View返回true,則說(shuō)明被消費(fèi)了扭屁,否則沒(méi)有消費(fèi)算谈,繼續(xù)下發(fā),直到最后一個(gè)是View料滥,即2的情況
//2然眼,如果子View不包含子View了,dispatchTransformedTouchEvent會(huì)調(diào)用View的dispatchTouchEvent方法葵腹,進(jìn)而調(diào)用onTouchEvent如果返回true高每,說(shuō)明被消費(fèi)了,否則沒(méi)有消費(fèi)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
//是否在執(zhí)行動(dòng)畫
private static boolean canViewReceivePointerEvents(View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
此時(shí)如果事件沒(méi)有被取消践宴,則會(huì)遍歷所有的子控件鲸匿,看哪一個(gè)能接收這個(gè)事件,條件有兩個(gè)浴井,即控件是否有動(dòng)畫在執(zhí)行晒骇,和是否落在觸摸的區(qū)域。getTouchTarget這個(gè)方法用于查找這個(gè)View是否在當(dāng)前的ViewGroup中磺浙。
private TouchTarget getTouchTarget(View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
當(dāng)View被添加到ViewGroup中時(shí)洪囤,會(huì)通過(guò)鏈表結(jié)構(gòu),將View封裝成一個(gè)TouchTarget的鏈表節(jié)點(diǎn)撕氧,這里是遍歷鏈表瘤缩,如果找到了,返回這個(gè)TouchTarget節(jié)點(diǎn)伦泥,并跳出上面的遍歷循環(huán)剥啤。
此時(shí),流程執(zhí)行到dispatchTransformedTouchEvent方法不脯,它是處理事件繼續(xù)向下分發(fā)的關(guān)鍵方法府怯。
//傳遞到子View的事件
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//是否消費(fèi)了事件
final boolean handled;
final int oldAction = event.getAction();
//取消事件
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//沒(méi)有子View消費(fèi)事件,看自己消不消費(fèi)
if (child == null) {
//直接調(diào)用父類的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//如果有子View防楷,將cancle事件傳遞到子View中
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//計(jì)算即將被傳遞的點(diǎn)的數(shù)量
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//如果事件沒(méi)有相應(yīng)的點(diǎn)牺丙,那么丟棄該事件
if (newPointerIdBits == 0) {
return false;
}
//保存坐標(biāo)轉(zhuǎn)換后的MotionEvent
final MotionEvent transformedEvent;
//如果事件點(diǎn)的數(shù)量一致
if (newPointerIdBits == oldPointerIdBits) {
//子元素為空,或有一個(gè)單位矩陣
if (child == null || child.hasIdentityMatrix()) {
//空的情況
if (child == null) {
//調(diào)用父類的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//獲取xy方向的偏移量(使用scrollTo或scrollBy滾動(dòng))
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
//將MotionEvent進(jìn)行坐標(biāo)變換
event.offsetLocation(offsetX, offsetY);
//變換后的MotionEvent傳給子View
handled = child.dispatchTouchEvent(event);
//復(fù)位MotionEvent以便之后再次使用
event.offsetLocation(-offsetX, -offsetY);
}
//返回是否消費(fèi)
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
//如果沒(méi)有包含子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冲簿,則調(diào)用子View的dispatchTouchEvent往下分發(fā)
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
這里先對(duì)父類/子類和父View/子View名詞做下強(qiáng)調(diào)粟判,前一組指的是繼承關(guān)系,后一組是包含關(guān)系峦剔。
- 如果上面遍歷找到接收的子View不為null档礁,則調(diào)用子View的dispatchTouchEvent方法,將事件傳遞給子View吝沫。
- 如果子View為null呻澜,事件不再往下傳遞,調(diào)用父類惨险,即View的dispatchTouchEvent方法易迹。
我們知道,控件要么是ViewGroup平道,要么是View。這意味著供炼,無(wú)論是ViewGroup還是View一屋,如果進(jìn)入了View類的dispatchTouchEvent方法,也就開始是否消費(fèi)事件的流程了袋哼。
我們回頭再分析View的dispatchTouchEvent方法冀墨。先假設(shè)handled最后返回了true,則dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)判斷滿足涛贯。
......
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//子View接收到事件的時(shí)間戳
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
//找到在列表中的角標(biāo)
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//如果子view消費(fèi)了诽嘉,把消費(fèi)了事件的View對(duì)應(yīng)的消費(fèi)節(jié)點(diǎn),賦值給newTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
//如果此時(shí)觸發(fā)沒(méi)有找到接收的View弟翘,將最近一次的目標(biāo)引用虫腋,作為當(dāng)前事件的的目標(biāo)引用
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
}
接收ACTION_DOWN事件的控件找到了,于是稀余,通過(guò)addTouchTarget方法悦冀,一開始定義的mFirstTouchTarget變量便被賦值,指向?qū)?yīng)的控件睛琳,并終止對(duì)子View的遍歷盒蟆。如果上面的handled返回了false,而且還有子View的話师骗,將繼續(xù)遍歷尋找历等。
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
//將目標(biāo)控件構(gòu)建成一個(gè)TouchTarget 節(jié)點(diǎn)
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//賦值給mFirstTouchTarget
mFirstTouchTarget = target;
return target;
}
如果找尋到最后,mFirstTouchTarget 仍舊為null辟癌,回看到我們一開始的分析寒屯,intercepted將置為ture,同一輪中后面所有的事件都被默認(rèn)攔截了愿待。也不會(huì)再遍歷包含的子View了浩螺。
......
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
//沒(méi)有子View消費(fèi)此事件
if (mFirstTouchTarget == null) {
//child為null靴患,會(huì)執(zhí)行View的dispatchTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
//往后的事件都分發(fā)給mFirstTouchTarget
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;
//mFirstTouchTarget 消費(fèi)事件
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;
}
}
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)鍵,無(wú)論mFirstTouchTarget 是不是null要出,后面的事件都不會(huì)再遍歷找尋子View鸳君。
當(dāng)mFirstTouchTarget 不為null時(shí),后面的事件都直接交給mFirstTouchTarget這個(gè)控件患蹂。
當(dāng)mFirstTouchTarget 為null時(shí)或颊,事件將由自己做消費(fèi)判斷。
出現(xiàn)第二種情形包含3種可能:
- ViewGroup沒(méi)有子View传于。
- ViewGroup所有的子View都沒(méi)有落在觸摸的范圍囱挑。
- 子View的dispatchTouchEvent返回false,一般在onTouchEvent中返回沼溜。
相同的是平挑,最終都會(huì)調(diào)用dispatchTransformedTouchEvent方法,但傳入的參數(shù)不同系草,最主要是第3個(gè)參數(shù)通熄,一個(gè)傳入null菲驴,表示自己消費(fèi)昆汹,一個(gè)傳入target.child,目標(biāo)控件消費(fèi)锉罐。但最終都進(jìn)入View的dispatchTouchEvent方法能耻。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo封裝了手勢(shì)監(jiān)聽赏枚,觸摸監(jiān)聽等監(jiān)聽接口
ListenerInfo li = mListenerInfo;
//設(shè)置的OnTouchListener
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果上面result不為true,執(zhí)行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
如源碼所示晓猛,OnTouchListener優(yōu)先級(jí)大于onTouchEvent饿幅,而且隨后的分析將看到,onClick是在onTouchEvent中被調(diào)用的戒职,所以诫睬,如果setOnTouchListener的onTouch返回true,那么onTouchEvent方法不會(huì)執(zhí)行帕涌,setOnClickListener的onClick回調(diào)也不會(huì)執(zhí)行摄凡。
我們依舊假設(shè)OnTouchListener返回false,事件進(jìn)入onTouchEvent方法蚓曼。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
//不可用狀態(tài)也消費(fèi)
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//只要可點(diǎn)擊就返回true
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//設(shè)置了代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//注意if作用域結(jié)束后返回true
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {//長(zhǎng)按則不響應(yīng)點(diǎn)擊事件
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//點(diǎn)擊事件進(jìn)入隊(duì)列執(zhí)行
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
//按下
case MotionEvent.ACTION_DOWN:
//是否長(zhǎng)按
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//狀態(tài)改為按下亲澡,比如button這時(shí)會(huì)變背景,需要刷新ui
setPressed(true, x, y);
checkForLongClick(0);//檢查并保存是否長(zhǎng)按狀態(tài)
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
//是否超出View的范圍
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
//移除長(zhǎng)按的監(jiān)聽
removeLongPressCallback();
//不是按下狀態(tài)
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
我們可以得出這樣的結(jié)論:View的enable屬性并不影響事件的消費(fèi)纫版,只要CLICKABLE和LONG_CLICKABLE有一個(gè)為true床绪,onTouchEvent就會(huì)返回ture,將事件消費(fèi)。
ACTION_UP事件發(fā)生時(shí)癞己,如果外部進(jìn)行setClickListener膀斋,將觸發(fā)performClick(),回調(diào)onClick方法痹雅。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//回調(diào)onClick方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
View的LONG_CLICKABLE默認(rèn)為false仰担,不可點(diǎn)擊的View,CLICKABLE默認(rèn)為false绩社,比如TextView摔蓝,可點(diǎn)擊的View,CLICKABLE則為true愉耙,比如Button贮尉。但是,我們知道View有監(jiān)聽點(diǎn)擊的方法朴沿,setOnLongClickListener會(huì)將LONG_CLICKABLE置為true猜谚,setOnClickListener將CLICKABLE置為true。
public void setOnLongClickListener(OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
到這里赌渣,整個(gè)事件分發(fā)的流程分析完畢龄毡。事件回調(diào)優(yōu)先級(jí)順序也清晰了:
onTouch > onTouchEvent > onClick