Android版本:7.0(API27)android
[TOC]bash
當一個點擊操做發生時,事件最早傳遞給當前的Activity,由Activity的dispatchTouchEvent來進行事件分發,具體的工做由Activity內部的Window來完成。Window會將事件傳遞給decor view,decor view通常就是當前界面的底層容器(即setContentView所設置的View的父容器)。咱們先從Activity的dispatchTouchEvent分析。app
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
首先事件開始交給Activity所附屬的Window進行分發,若是返回true,整個事件循環就結束了,返回false意味着事件沒人處理,全部View的onTouchEvent都返回fasle,那麼Activity的onTouchEvent就會被調用。ide
接下來看Window是如何將事件傳遞給ViewGroup的。經過源碼咱們知道,Window是個抽象類,而Window的superDispatchTouchEvent方法也是個抽象方法,所以咱們必須找到Window的實現類才行。源碼分析
Window.superDispatchTouchEvent動畫
public abstract boolean superDispatchTouchEvent(MotionEvent event);
複製代碼
那麼到底Window的實現類是什麼?實際上是PhoneWindow,這一點從Window的源碼中也能夠看出來,在Window的說明中,有這麼一段話:ui
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window
複製代碼
PhoneWindow.superDispatchTouchEventthis
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
複製代碼
PhoneWindow將事件直接傳遞給了DecorView,這個DecorView是什麼呢?請看下面:spa
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
複製代碼
咱們知道,經過((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)這種方式就能夠獲取Activity所設置的View,這個mDecor顯然就是getWindow().getDecorView()返回的View,而咱們經過setContentView設置的View是它的一個子View。目前事件傳遞到了DecorView這裏,猶豫DecorView繼承自FrameLayout且是父View,因此最終事件傳遞給View。換句話說,事件確定會傳遞到View,否則應用如何響應點擊事件?因此,重點就到了ViewGroup中事件分發機制了。rest
基礎知識:
事件分發會調用ViewGroup.dispatchTouchEvent方法,該方法的核心是按照以下步驟分析:
該方法中有兩個重要的字段:
intercepted:ViewGroup是否攔截事件,true表示攔截;
mFirstTouchTarget:是否有子View消費了down事件,若是有則mFirstTouchTarget != null;
// 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();
}
複製代碼
首先這裏先判斷事件是否爲DOWN事件,若是是,則初始化,resetTouchState方法中把mFirstTouchTarget置爲null。因爲一個完整的事件序列是以DOWN開始,以UP結束,因此若是是DOWN事件,那麼說明是一個新的事件序列,因此須要初始化以前的狀態。
這裏的mFirstTouchTarget很是重要,後面會說到當ViewGroup的子元素成功處理事件的時候,mFirstTouchTarget會指向子元素,這裏要留意一下。
// 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;
}
複製代碼
經過源碼分析,在以下兩種狀況下會調用ViewGroup的nInterceptTouchEvent檢查ViewGroup是否攔截事件:
若是ViewGroup攔截了事件則intercepted = true。
注:此處還有一個FLAG_DISALLOW_INTERCEPT屬性,爲了流程清晰,咱們暫且不分析,後續再說明。
若是ViewGroup不攔截事件且事件是ACTION_DOWN事件,就會執行這個雙if語句中:
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) {
}
}
複製代碼
這個雙if語句的核心做用是:將ACTION_DOWN分發到本身的子View中,找到消費DOWN事件的子View,並賦值給mFirstTouchTarget;若是沒有子View處理down事件那麼mFirstTouchTarget == null。
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--) { // ------------------------------------1
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)) {// -----------------------2
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)) {// -----------------------3
// 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(); } 複製代碼
這裏獲取了childrenCount的值,表示該ViewGroup內部有多少個子View,若是有子View就開始遍歷全部子View判斷是否要把事件分發給子View。
代碼也比較長,咱們只關注重點部分:
代碼標記1
是一個for循環,這裏表示對全部的子View進行循環遍歷,因爲以上判斷了ViewGroup不對事件進行攔截,那麼在這裏就要對ViewGroup內部的子View進行遍歷,一個個地找到能接受事件的子View,這裏注意到它是倒序遍歷的,即從最上層的子View開始往內層遍歷,這也符合咱們日常的習慣,由於通常來講咱們對屏幕的觸摸,確定是但願最上層的View來響應的,而不是被覆蓋這的底層的View來響應,不然這有悖於生活體驗。
代碼標記2
根據方法名字咱們得知這個判斷語句是判斷觸摸點位置是否在子View的範圍內或者子View是否在播放動畫,若是均不符合則continue,表示這個子View不符合條件,開始遍歷下一個子View
代碼標記3
這裏調用了dispatchTransformedTouchEvent()方法,這個方法有什麼用呢?
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 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;
}
複製代碼
當child != null時,將down事件傳遞到子view的dispatchTouchEvent;不然由ViewGroup的父類dispatchTouchEvent處理。 前面的分析條件是有子View的狀況,全部child != null,down事件傳遞到子view的dispatchTouchEvent處理。
若是子View的onTouchEvent()返回true,那麼就是消耗了Down事件,接着會調用addTouchTarget方法:
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
複製代碼
該方法中對mFirstTouchTarget = target進行了賦值,這也證明了前面所說的 「若是子View消耗了事件,那麼mFirstTouchTarget不爲null」。
小結:
整一個if(!canceled && !intercepted){ … }代碼塊所作的工做就是對ACTION_DOWN事件的特殊處理。由於ACTION_DOWN事件是一個事件序列的開始,因此咱們要先找到可以處理這個事件序列的一個子View,若是一個子View可以消耗事件,那麼mFirstTouchTarget會指向子View,若是全部的子View都不能消耗事件,那麼mFirstTouchTarget將爲null。
上面代碼的整個過程目的只有一個,找到消費down事件的子View,若是找到mFirstTouchTarget != null。下面就是根據mFirstTouchTarget對move、up事件進行分發。
// 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;
}
}
複製代碼
(1)mFirstTouchTarget == null 說明沒有子View消耗down事件,那麼後續全部事件都交給ViewGroup的super.dispatchTouchEvent去處理;
(2)mFirstTouchTarget != null
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)
複製代碼
這裏的判斷就是區分了ACTION_DOWN事件和別的事件,由於在上面的分析咱們知道,若是子View消耗了ACTION_DOWN事件,那麼alreadyDispatchedToNewTouchTarget和newTouchTarget已經有值了,因此就直接置handled爲true並返回;那麼若是alreadyDispatchedToNewTouchTarget和newTouchTarget值爲null,那麼就不是ACTION_DOWN事件,便是ACTION_MOVE、ACTION_UP等別的事件,這些事件都會傳遞給消耗down事件的子View即mFirstTouchTarget。
FLAG_DISALLOW_INTERCEPT標誌位是經過requestDisallowInterceptTouchEvent方法來設置
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); } } 複製代碼
一旦設置了FLAG_DISALLOW_INTERCEPT爲true,那麼ViewGroup就不能經過onInterceptTouchEvent返回true來攔截任何事件了。這一點在分析"檢查ViewGroup是否要攔截事件"中能夠看到。