本篇博文是Android点击事件分发机制系列博文的第三篇,主要是从解读ViewGroup类的源码入手,根据源码理清ViewGroup点击事件分发原理,明白ViewGroup和View点击事件分发的关系,并掌握ViewGroup点击事件分法机制。特别声明的是,本源码解读是基于最新的Android6.0版本。java
各位童鞋能够参考下面连接进行系统学习
(一)Android6.0触摸事件分发机制解读markdown
(二)Android6.0源码解读之View点击事件分发机制app
(三)Android6.0源码解读之ViewGroup点击事件分发机制ide
(四)Android6.0源码解读之Activity点击事件分发机制源码分析
ViewGroup事件分发中的三个重要方法的源码解析
关于ViewGroup事件分发,咱们重点须要解读dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法。ViewGroup比View多了一个onInterceptTouchEvent拦截事件方法,该方法源码默认返回false,即ViewGroup默认不拦截任何事件。学习
(一)dispatchTouchEvent源码解析
/** * 重写了父类View的dispatchTouchEvent方法 */ @Override 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; // 手指按下去进行一些初始化的处理 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;// 是否被拦截 // 若是事件类型ACTION_DOWN或者mFirstTouchTarget不为空 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) {// 当disallowIntercept为false时 intercepted = onInterceptTouchEvent(ev);// 这里是重点,它会调用onInterceptTouchEvent方法,当该方法为true时拦截,为false时不拦截 // 所谓的拦截,是指按下去自身以及之后的后续事件move up,拦截下来给本身onTouch使用 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) {// 这里的canceled和intercepted都为false时,条件成立,也就是说不拦截 // 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) {// 若是子控件的个数不为0 且 newTouchTarget为空 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. // 考虑到两个View交叉重合的状况,下面的先放进集合,可是按常理说咱们手指先按到上面的,这里作了一个倒序 final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) {// 遍历ViewGroup中的子控件 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();// 拿到X、Y位置 mLastTouchDownY = ev.getY(); // newTouchTarget用到了单向链表 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) {// 若是newTouchTarge为空 且 mFirstTouchTarget不为空 // 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.分发给touch targets if (mFirstTouchTarget == null) { // 没有触摸的对象就把它当作一个普通的View // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);// 第三个参数本应为child,这里是null意味着须要调用父类View的dispatchTouchEvent方法,而后调用onTouch方法 } 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; }
ViewGroup拦截状况源码分析
首先咱们来看一下第34行~48行的代码,ViewGroup在以下两种状况下会判断是否要拦截当前事件:动画
事件类型为ACTION_DOWN或者 mFirstTouchTarget != nullui
即,当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget 会被赋值并指向子元素,换句话说,当ViewGroup不拦截事件并将事件交由子元素处理时mFirstTouchTarget != null。反过来,一旦事件由当前的ViewGroup拦截时,mFirstTouchTarget != null条件就不成立。那么当ACTION_MOVE和UP事件到来时,因为actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null这个条件为false,致使ViewGroup的onInterceptTouchEvent不会再被调用,而且同一序列中的其余事件都会默认交给它处理。
另外,这里有一种特殊状况,咱们看36行代码,有个FLAG_DISALLOW_INTERCEPT标记为,这个标记是经过requestDisallowInterceptTouchEvent()方法来设置的,通常用在子View中。若是咱们经过reqeustDisallowInterceptTouchEvent()方法设置了FLAG_DISALLOW_INTERCEPT标记位后,ViewGroup将没法拦截除了ACTION_DOWN之外的其余方法(即调用该方法并不影响ACTION_DOWN事件处理)。由于ViewGroup会在ACTION_DWON事件到来时作重置状态操做,这里从代码第22~29行能够看出。this
requestDisallowInterceptTouchEvent源码解析
/** * {@inheritDoc} */ 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); } }
取消、清理、重置以前的触摸状态
/** * Cancels and clears all touch targets. */ private void cancelAndClearTouchTargets(MotionEvent event) { if (mFirstTouchTarget != null) {// 若是保存的第一个触摸View对象不为空 boolean syntheticEvent = false; if (event == null) { final long now = SystemClock.uptimeMillis(); event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); syntheticEvent = true; } for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { resetCancelNextUpFlag(target.child); dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); } clearTouchTargets(); if (syntheticEvent) { event.recycle(); } } } /** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; }
所以咱们能够得出以下结论:spa
1.当ViewGroup决定拦截事件后,那么点击事件将会默认交给它处理而且再也不调用它的onInterceptTouchEvent方法,FLAG_DISALLOW_INTERCEPT这个标记的做用是ViewGroup再也不拦截事件,前提是ViewGroup不拦截ACTION_DOWN事件处理。
2.若是事件可以传递到当前的ViewGroup,且咱们要提早处理全部点击事件,应该选择dispatchTouchEvent方法,由于只有这个方法能确保每次都会被调用;而onInterceptTouchEvent()却没法保证每次事件都会被调用。
3.FLAG_DISALLOW_INTERCEPT标记位能够用于解决滑动冲突问题。
ViewGroup不拦截状况源码分析
ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理。先来看下代码64行(!canceled && !intercepted)这里的canceled和intercepted都为false时,条件成立,也就是说不拦截。接下来74行的条件判断:
actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE
在该if条件内,看到第86行,若是newTouchTarget == null && childrenCount != 0,即子控件的个数不为0 且 newTouchTarget为空,在95行中遍历整个ViewGroup中的子控件,这里的集合作了个倒序排列,若是两个View交叉覆盖在一块儿,下面的子控件先放进集合,由于后被添加的子控件会浮在上面,一般咱们会但愿点击的时候最上层的那个组件先去响应事件。接着105行代码开始判断子控件是否可以接收到点击事件,主要依赖于两个条件:第一子控件是否在播动画;第二点击事件是否落在子控件的区域内。若是某个子控件知足这两个条件,那么事件就会传递给它来处理。
buildOrderedChildList方法解析
/** * 实现倒序排序 * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children, * sorted first by Z, then by child drawing order (if applicable). This list must be cleared * after use to avoid leaking child Views. * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated * children. */ ArrayList<View> buildOrderedChildList() { final int count = mChildrenCount; if (count <= 1 || !hasChildWithZ()) return null; if (mPreSortedChildren == null) { mPreSortedChildren = new ArrayList<View>(count); } else { mPreSortedChildren.ensureCapacity(count); } final boolean useCustomOrder = isChildrenDrawingOrderEnabled(); for (int i = 0; i < mChildrenCount; i++) { // add next child (in child order) to end of list int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i; View nextChild = mChildren[childIndex]; float currentZ = nextChild.getZ(); // insert ahead of any Views with greater Z int insertIndex = i; while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { insertIndex--; } mPreSortedChildren.add(insertIndex, nextChild); } return mPreSortedChildren; }
接着看代码,129行经过dispatchTransformedTouchEvent()这一重要方法(后面有详细分析),判断是否有子控件,若是有子控件则执行内部的操做,并找到最终触摸的对象,经过addTouchTarget方法赋值给newTouchTarget。在dispatchTransformedTouchEvent()方法中,若是子控件的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环,详见148行代码。一样若是dispatchTouchEvent()方法返回false,ViewGroup就会把事件分发给下一个子控件(若是还有下一个子控件)。
mFirstTouchEvent的真正赋值实际上是在addTouchTarget方法中完成的,mFirstTouchEvent实际上是一个单链表结构,若是mFirstTouchEvent为null,那么ViewGroup就会默认拦截下来同一序列中全部的点击事件。
addTouchTarget方法解析
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */ private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); // 往链表中插入一个元素 target.next = mFirstTouchTarget;// 将mFirstTouchTarget赋值给target.next mFirstTouchTarget = target;// 将target赋值给mFirstTouchTarget return target; }
接着咱们看到171行代码中,若是mFirstTouchEvent为null,也就是说要么ViewGroup中没有子控件,要么是子控件处理了点击事件,可是在dispatchTouchEvent中返回了false(子控件在onTouchEvent中返回了false),那么ViewGroup就会本身处理点击事件,须要说明的是175行代码中,第三个参数本应为child,这里是null意味着须要调用父类View的dispatchTouchEvent方法,而后调用onTouch方法。
TouchTarget 内部类源码解析
/* Describes a touched view and the ids of the pointers that it has captured. * 链表实现的内部类,解决多指触控问题用来指定当前触摸的对象,多个手指触控(0~31个手指) * This code assumes that pointer ids are always in the range 0..31 such that * it can use a bitfield to track which pointer ids are present. * As it happens, the lower layers of the input dispatch pipeline also use the * same trick so the assumption should be safe here... */ private static final class TouchTarget { private static final int MAX_RECYCLED = 32; private static final Object sRecycleLock = new Object[0]; private static TouchTarget sRecycleBin; private static int sRecycledCount; public static final int ALL_POINTER_IDS = -1; // all ones // The touched child view. public View child; // The combined bit mask of pointer ids for all pointers captured by the target. public int pointerIdBits; // The next target in the target list. public TouchTarget next; private TouchTarget() { } public static TouchTarget obtain(View child, int pointerIdBits) { final TouchTarget target; synchronized (sRecycleLock) { if (sRecycleBin == null) { target = new TouchTarget(); } else { target = sRecycleBin; sRecycleBin = target.next; sRecycledCount--; target.next = null; } } target.child = child; target.pointerIdBits = pointerIdBits;// 手指的ID return target; } public void recycle() { synchronized (sRecycleLock) { if (sRecycledCount < MAX_RECYCLED) { next = sRecycleBin; sRecycleBin = this; sRecycledCount += 1; } else { next = null; } child = null; } } }
相当重要的dispatchTransformedTouchEvent方法解析
/** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */ 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) {// 若是child为空,则调用本身的分发方法 handled = super.dispatchTouchEvent(event); } else {// 不然调用child的分发方法 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) {// 若是ViewGroup中没有子控件,调用父类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()); } // 调用子控件的dispatchTouchEvent,有两种状况,若是是子控件是View,又分红两种状况(Button返回true,TextView返回false);若是是ViewGroup则进入递归了,又回到了这段代码,最终要么没有任何消耗事件的View,要么找到消费事件的View handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
该方法在dispatchTouchEvent()中被调用,用于将事件分发给子View处理。咱们重点看一下60~71行代码。在dispatchTransformedTouchEvent()方法一共有三个参数,其中第三个参数View child有时为null,有时不为null。61行代码中,child==null意味着事件没有被消费,ViewGroup中没有子控件须要调用父类View的dispatchTouchEvent方法,即super.dispatchTouchEvent(event)。
接着咱们关注下handled这个变量,能够发现dispatchTransformedTouchEvent()方法return handled,而handled的值实际上是取决于dispatchTransformedTouchEvent()方法递归调用dispatchTouchEvent()方法的结果,也就是说在子控件中dispatchTouchEvent()方法的onTouchEvent()是否消费了Touch事件的返回值决定了dispatchTransformedTouchEvent()的返回值,从而决定mFirstTouchTarget是否为null,更进一步决定了ViewGroup是否处理Touch事件。
(二)onInterceptTouchEvent源码解析
public boolean onInterceptTouchEvent(MotionEvent ev) { return false;// 默认不拦截 }
你没有看错,这方法就是简简单单的一个布尔值返回,当返回true时,对事件进行拦截,返回false则不拦截。
(三)ViewGroup点击事件分发小结
Android点击事件分发是到达顶级View后(通常是ViewGroup),会调用ViewGroup的dispatchTouchEvent方法,其中它的onInterceptTouchEvent方法若是返回true,则会对事件传递进行拦截,事件由ViewGroup处理;若是onInterceptTouchEvent方法返回false,则表明不对事件进行拦截,默认返回false,即全部的ViewGroup都是默认不拦截的。则此时子View中的dispatchTouchEvent方法将被调用,到此,事件已经由顶级View传递给了下一层的View,接下来的过程是一个递归循环的过程,和顶级View事件分发过程是一致的,直到完成整个事件分发。