Activity的Touch事件事實上是調(diào)用它內(nèi)部的ViewGroup的Touch事件命锄,可以直接當成ViewGroup處理蒜绽。View在ViewGroup內(nèi),ViewGroup也可以在其他ViewGroup內(nèi),這時候把內(nèi)部的ViewGroup當成View來分析俐筋。ViewGroup的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent严衬、onTouchEvent澄者。View的相關事件只有兩個:dispatchTouchEvent、onTouchEvent请琳。
前面一篇文章中我們分析了App返回按鍵的分發(fā)流程粱挡,從Native層到ViewRootImpl層到DocorView層到Activity層,以及在Activity中的dispatchKeyEvent方法中分發(fā)事件俄精,最終調(diào)用了Activity的finish方法询筏,即銷毀Activity,所以一般情況下假如我們不重寫Activity的onBackPress方法或者是onKeyDown方法竖慧,當我們按下并抬起返回按鍵的時候默認都是銷毀當前Activity嫌套。而本文中我們主要介紹觸摸事件的分發(fā)流程,從Native層到Activity層觸摸事件的分發(fā)了流程和按鍵的分發(fā)事件都是類似的圾旨,這里我們可以根據(jù)異常堆棧信息看一下灌危。
at com.example.aaron.helloworld.MainActivity.dispatchTouchEvent(MainActivity.java:103)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2359)
at android.view.View.dispatchPointerEvent(View.java:8698)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4530)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4388)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3924)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3977)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3943)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4053)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3951)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4110)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3924)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3977)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3943)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3951)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3924)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6345)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6301)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6254)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6507)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
這樣經(jīng)過一系列的方法調(diào)用之后最終調(diào)用了Activity的dispatchTouchEvent方法,而我們也是從Activiyt的dispatchTouchEvent方法開始對觸摸事件的分發(fā)進行分析碳胳。
在具體查看Activity的dispatchTouchEvent方法之前我們先簡單介紹一下觸摸事件勇蝙,觸摸事件是由一個觸摸按下事件、N個觸摸滑動事件和一個觸摸抬起事件組成的挨约,通常的一個觸摸事件中只能存在一個觸摸按下和一個觸摸抬起事件味混,但是觸摸滑動事件可以有零個或者多個。好了诫惭,知道這個概念以后翁锡,下面我們就具體看一下Activity中的dispatchTouchEvent的實現(xiàn)邏輯。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
在看一下dispatchTouchEvent方法之前我們首先需要解釋一下MotionEvent的概念夕土。MotionEvent是一個觸摸動作的封裝馆衔,里面包含了觸摸動作的類型,以及操作等屬性怨绣,我們具體的可以看一下MotionEvent的說明:
Object used to report movement (mouse, pen, finger, trackball) events. Motion events may hold either absolute or relative movements and other data, depending on the type of device.
然后在dispatchTouchEvent方法中角溃,會首先判斷MotionEvent的動作類型,也就是我們的觸目動作的類型篮撑,判斷其是否是“按下”操作减细,若是的湖澤,則執(zhí)行onUserInteraction方法赢笨,這個方法又是實現(xiàn)了什么邏輯呢未蝌?
public void onUserInteraction() {
}
可以發(fā)現(xiàn)其在Activity中只是一個簡單的空實現(xiàn)方法驮吱,同樣的我們可以看一下該方法的介紹:
Called whenever a key, touch, or trackball event is dispatched to the activity. Implement this method if you wish to know that the user has interacted with the device in some way while your activity is running. This callback and {@link #onUserLeaveHint} are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.
理解上就是用戶在觸屏點擊,按home萧吠,back左冬,menu鍵都會觸發(fā)此方法。
回到Activity的dispatchTouchEvent方法中纸型,我們調(diào)用了getWindow().suerDispatchTouchEvent()方法又碌,我們分析過Activity的加載繪制流程,而這里的getWindow()就是返回Activity中的mWindow對象绊袋,而我們知道Activity中的mWindow對象就是一個PhoneWindow的實例毕匀。并且這里的window.superDispatchTouchEvent若返回值為ture,則直接返回true癌别,否則的話會執(zhí)行Activity的onTouchEvent方法皂岔,繼續(xù)我們看一下PhoneWindow的superDispatchTouchEvent方法。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
可以看到在PhoneWindow中的superDispatchTouchEvent方法中調(diào)用的是mDecor.superDispatchTouchEvent方法展姐,而這里的mDecor是我們Activity顯示的ViewTree的根View躁垛,并且mDecor是一個FrameLayout的子類,所以這里我們看一下mDecor的superDispatchTouchEvent方法圾笨。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
...
}
在DecorView的superDispatchTouchEvent方法中我們調(diào)用了super.dispatchTouchEvent方法教馆,而我們的DecorView繼承于FrameLayout,但是經(jīng)過查看之后我們知道FrameLayout中并沒有實現(xiàn)dispatchTouchEvent方法擂达,而由于我們的FrameLayout繼承于ViewGroup土铺,所以這里的dispatchTouchEvent方法應該就是ViewGroup的dispatchTouchEvent方法。
好了板鬓,這里先暫時說一下Acitivty中的事件分發(fā)流程
ViewRootImpl層的事件分發(fā)會首先調(diào)用Activity的dispatchTouchEvent方法悲敷;
Activity的dispatchTouchEvent方法中會通過Window.superDispatchTouchEvent方法將事件傳遞給DecorView即ViewGroup。
若window的superDispatchTouchEvent方法返回true俭令,則事件分發(fā)完成后德,Activity的dispatchTouchEvent直接返回為true,否則的話調(diào)用Activity的onTouchEvent方法抄腔,并且Acitivty的dispatchTouchEvent返回值與Activity的onTouchEvent返回值一致瓢湃。
下面我們在繼續(xù)看一下ViewGroup的dispatchTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
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) {
// 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.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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;
}
// 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) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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 = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
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);
// 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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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;
}
resetCancelNextUpFlag(child);
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();
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();
}
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;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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;
}
}
// 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;
}
前面我們知道觸摸事件是由一個觸摸按下事件赫蛇,一個觸摸抬起事件和N個觸摸滑動事件組成的绵患,而這里的觸摸按下事件就是這里的ACTION_DOWN,同時友誼ACTION_DOWN是一系列事件的開端棍掐,所以我們在ACTION_DOWN時進行一些初始化操作藏雏,從上面源碼中注釋也可以看出來,清除以往的Touch狀態(tài)然后開始新的手勢作煌。并在在cancelAndClearTouchTargets(ev)方法中將mFirstTouchTarget設置為了null掘殴,接著在resetTouchState()方法中重置Touch狀態(tài)標識。
然后標記ViewGroup是否攔截Touch事件的傳遞粟誓,if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)這一條判斷語句說明當事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經(jīng)找到能夠接收touch事件的目標組件)時if成立奏寨,否則if不成立,然后將intercepted設置為true鹰服,也即攔截事件病瞳。這里說明一下ViewGroup中的onInterceptTouchEvent方法是ViewGroup中特有的方法用于表示是否攔截觸摸事件,返回為true的話則表示攔截事件悲酷,事件不在向子View中分發(fā)套菜,若范圍為false的話,則表示不攔截事件设易,繼續(xù)分發(fā)事件逗柴。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
一般的我們可以在自定義的ViewGroup中重寫該方法,用于攔截事件的分發(fā)顿肺。而當我們在父ViewGroup重寫該方法返回為true執(zhí)行事件攔截的邏輯的時候戏溺,可以在子View中通過調(diào)用requestDisallowInterceptTouchEvent方法,重新設置父ViewGroup的onInterceptTouchEvent方法為false屠尊,不攔截對事件的分發(fā)邏輯旷祸。
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
比如常見的向我們的ViewPager中由于需要處理左右滑動事件從而在其onInterceptTouchEvent方法中重寫了返回值,返回為true讼昆,攔截對事件的處理邏輯托享,但是若這時候ViewPager中嵌套了ListView,則listView也需要處理觸摸事件的邏輯浸赫,但是ViewPager中已經(jīng)重寫了onInterceptTouchEvent方法嫌吠,這時候怎么辦呢?幸運的是ListView也在內(nèi)部的實現(xiàn)中調(diào)用了requestDisallowInterceptTouchEvent方法掺炭,保證自身獲得對觸摸事件的處理辫诅。
然后在代碼中我們判斷childrenCount個數(shù)是否不為0,繼續(xù)我們獲取子View的list集合preorderedList涧狮;最后通過一個for循環(huán)倒序遍歷所有的子view炕矮,這是因為preorderedList中的順序是按照addView或者XML布局文件中的順序來的,后addView添加的子View者冤,會因為Android的UI后刷新機制顯示在上層肤视;假如點擊的地方有兩個子View都包含的點擊的坐標,那么后被添加到布局中的那個子view會先響應事件涉枫;也就是說后被添加的子view會浮在上層邢滑,點擊的時候最上層的那個組件先去響應事件。
然后代碼通過調(diào)用getTouchTarget去查找當前子View是否在mFirstTouchTarget.next這條target鏈中的某一個targe中愿汰,如果在則返回這個target困后,否則返回null乐纸。在這段代碼的if判斷通過說明找到了接收Touch事件的子View,即newTouchTarget摇予,那么汽绢,既然已經(jīng)找到了,所以執(zhí)行break跳出for循環(huán)侧戴。如果沒有break則繼續(xù)向下執(zhí)行宁昭,這里你可以看見一段if判斷的代碼if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),那么這個方法又是執(zhí)行什么邏輯的呢酗宋?
在該方法中為一個遞歸調(diào)用积仗,會遞歸調(diào)用dispatchTouchEvent()方法。在dispatchTouchEvent()中如果子View為ViewGroup并且Touch沒有被攔截那么遞歸調(diào)用dispatchTouchEvent()蜕猫,如果子View為View那么就會調(diào)用其onTouchEvent()寂曹。dispatchTransformedTouchEvent方法如果返回true則表示子View消費掉該事件。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
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;
}
然后在在ViewGroup的dispatchTransformedTouchEvent方法中丹锹,調(diào)用了該ViewGroup的child View的dispatchTouchEvent方法稀颁,若其子View也是ViewGroup,則重復執(zhí)行ViewGroup的dispatchTouchEvent方法楣黍,若其子View是View匾灶,則執(zhí)行View的dispatchTouchEvent方法。
但這里大概分析了一下ViewGroup的事件分發(fā)流程
首先在android的事件分發(fā)流程中租漂,通過調(diào)用Activity的dispatchTouchEvent阶女,事件會首先被派發(fā)是先傳遞到最頂級的DecorView也就是ViewGroup,再由ViewGroup遞歸傳遞到View的哩治。
在ViewGroup中可以通過設置onInterceptTouchEvent方法對事件傳遞進行攔截秃踩,onInterceptTouchEvent方法返回true代表不允許事件繼續(xù)向子View傳遞,返回false代表不對事件進行攔截业筏,默認返回false憔杨。
下面我們繼續(xù)看一下View的dispatchTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
//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;
}
View的dispatchTouchEvent方法的內(nèi)容比較長蒜胖,我們重點看一下View對觸摸事件的處理邏輯消别,首先調(diào)用了onFilterTouchEventForSecurity(event)方法判斷當前的View是否被遮蓋,若沒有的話台谢,則判斷View的mListenerInfo城邊變量是否為空龙亲,而這里的mListenerInfo又是什么呢屋讶?通過分析源碼我們知道這里的mListenerInfo是通過setOnClickListener方法設置的锨天。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以當前View一旦執(zhí)行了setOnClickListener方法改View的mListenerInfo就不為空囱淋,若后有判斷了該View是否可點擊,最后是判斷View的onTouchListener的onTouch方法的返回值。
所以當我們?yōu)楫斍癡iew設置了OnTouchListener并且返回值為true的話纠亚,則直接執(zhí)行其onTouch方法塘慕,若onTouch方法返回為true的話,則直接返回不在執(zhí)行后續(xù)的View的onTouchEvent方法菜枷,否則繼續(xù)執(zhí)行View的onTouchEvent方法苍糠,而我們繼續(xù)看一下View的onTouchEvent方法的實現(xiàn)邏輯叁丧。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
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);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return 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)) {
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:
mHasPerformedLongPress = false;
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);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
在ACTION為MotionEvent.ACTION_UP時啤誊,我們經(jīng)過層層調(diào)用最終執(zhí)行了performClick,方法而這個方法中我們回調(diào)了View的OnClickListener的onClick方法拥娄。蚊锹。。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
總結:
? 事件分發(fā):public boolean dispatchTouchEvent(MotionEvent ev)
Touch 事件發(fā)生時 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法會以隧道方式(從根元素依次往下傳遞直到最內(nèi)層子元素或在中間某一元素中由于某一條件停止傳遞)將事件傳遞給最外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法稚瘾,并由該 View 的 dispatchTouchEvent(MotionEvent ev) 方法對事件進行分發(fā)牡昆。dispatchTouchEvent 的事件分發(fā)邏輯如下:
如果 return true,事件會分發(fā)給當前 View 并由 dispatchTouchEvent 方法進行消費摊欠,同時事件會停止向下傳遞丢烘;
如果 return false,事件分發(fā)分為兩種情況:
如果當前 View 獲取的事件直接來自 Activity些椒,則會將事件返回給 Activity 的 onTouchEvent 進行消費播瞳;
如果當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的 onTouchEvent 進行消費免糕。
如果返回系統(tǒng)默認的 super.dispatchTouchEvent(ev)赢乓,事件會自動的分發(fā)給當前 View 的 onInterceptTouchEvent 方法。
? 事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)
在外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系統(tǒng)默認的 super.dispatchTouchEvent(ev) 情況下石窑,事件會自動的分發(fā)給當前 View 的 onInterceptTouchEvent 方法牌芋。onInterceptTouchEvent 的事件攔截邏輯如下:
如果 onInterceptTouchEvent 返回 true,則表示將事件進行攔截松逊,并將攔截到的事件交由當前 View 的 onTouchEvent 進行處理躺屁;
如果 onInterceptTouchEvent 返回 false,則表示將事件放行经宏,當前 View 上的事件會被傳遞到子 View 上犀暑,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發(fā);
如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev)烛恤,事件默認會被攔截母怜,并將攔截到的事件交由當前 View 的 onTouchEvent 進行處理。
? 事件響應:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情況下 onTouchEvent 會被調(diào)用缚柏。onTouchEvent 的事件響應邏輯如下:
如果事件傳遞到當前 View 的 onTouchEvent 方法苹熏,而該方法返回了 false,那么這個事件會從當前 View 向上傳遞,并且都是由上層 View 的 onTouchEvent 來接收轨域,如果傳遞到上面的 onTouchEvent 也返回 false袱耽,這個事件就會“消失”,而且接收不到下一次事件干发。
如果返回了 true 則會接收并消費該事件朱巨。
如果返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。