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是否要拦截事件"中能够看到。