Android 事件分发机制源码攻略(二) —— ViewGroup篇
来源:互联网 发布:龙泉驿广电网络全称 编辑:程序博客网 时间:2024/06/06 03:52
ViewGroup
这一篇是续上篇Android 事件分发机制源码攻略(一) —— Activity篇 的ViewGroup,想了解Activity篇的也可以点击查看(本来应该是很快就发布这一篇了,结果被CSDN的不自动保存坑死了,拖了一周)。
这篇算是Android事件分发中最为关键的一篇,因为这里会分析哪些事件会被拦截,是以何种形式获取子View,以及对ACTION_DOWN后续事件传递等问题,都会在这里得到答案。好了,废话不多说,现在开始分析。
上一篇,我们走到的ViewGroup的dispatchTouchEvent()这个方法。 先来说下我待会的讲解思路,首先,我们可以通过目测判断这个方法很长,对于很长的源码,以我个人的经验最好是找出关键点,然后逐个击破。
如果是ACTION_DOWN事件,就会去寻找子View来处理,如果找不到子View来处理,就自己处理。
如果不是ACTION_DOWN事件,就会把这个事件传给处理了ACTION_DOWN事件的View来处理。
大致就这两个逻辑,虽说比较粗略,不过,这对于接下来看源码就足够了,并且源码有比较多的注释,基本上大致的方向是可以弄懂了。
@Override public boolean dispatchTouchEvent(MotionEvent ev) { ... //返回值的关键,注意留意handled的值发生改变的地方 boolean handled = false; //判断当前window是否有被遮挡,true为分发这个事件,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 // due to an app switch, ANR, or some other state change.//在新的事件开始(即是新的ACTION_DOWN事件),需要清除掉之前的状态以及设置mFirstTouchTarget=null; cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. final boolean intercepted; //子View唯一一个可以用来控制父类事件传递 //只有ACTION_DOWN事件跟mFirstTouchTarget不为空的情况,后面的讨论大多是围绕着mFirstTouchTarget来进行的 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //是否拦截事件,disallowIntercept为true是不拦截,false是拦截 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //一般重写onInterceptTouchEvent方法 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. //split是否分发给多个子View,默认为false final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //如果不被拦截即可进入或者不是ACTION_CANCEL事件 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; //只有ACTION_DOWN等事件能够进入 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. //获取按Z轴从大到小排序的子View列表 final ArrayList<View> preorderedList = buildTouchDispatchChildList(); //是否有自定义顺序,一般为false final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { //确认这个子View的下标 final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); //根据上面获得的下标,确认这个子View final View child = getAndVerifyPreorderedView( preorderedList, children, 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; } //是否获得可见,并且落在child的布局范围内 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //Child是否已经处理过事件了,有的话更改pointerIdBits值,并结束查找 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); //分发给View的dispatchTouchEvent 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(); //给mFirstTouchTarget赋值,该事件已经被子View确认处理了 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. // 没有子View处理,则自己处理 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. //处理除了ACTION_DOWN以外的事件 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; //如果这个事件被拦截了,intercepted为true 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; }
像这么长的代码,很多地方是可以跳过的,不过仔仔细细分析,特别是像Google出品的(个人愚见),因为这些东西考虑的方方面面比较多,而我们这个只是为了了解事件的分发,绘制那块我们不会过多涉及。(说跑题了)回到正题来,像这么长的代码,之前学习的时候,有个牛人是这么写的(个人总结)。
从结果出发,留意改变的结果的地方
上面的dispatchTouchEvent返回值是由handle决定,我们先来看第一处第8行代码
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(); } ... } return false;
这个onFilterTouchEventForSecurity方法如果返回false的话,基本上里面的代码都不用分析了,直接返回false。那我们进去看看这个方法做了什么。
/** * Filter the touch event to apply security policies. * * @param event The motion event to be filtered. * @return True if the event should be dispatched, false if the event should be dropped. * * @see #getFilterTouchesWhenObscured */ public boolean onFilterTouchEventForSecurity(MotionEvent event) { //noinspection RedundantIfStatement if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) { // Window is obscured, drop this touch. return false; } return true; }
这是一个安全策略方面的过滤,我们来看下这两个变量FILTER_TOUCHES_WHEN_OBSCURED、MotionEvent.FLAG_WINDOW_IS_OBSCURED是什么意思
/** * Indicates that the view should filter touches when its window is obscured. * Refer to the class comments for more information about this security feature. * {@hide} */ static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
/** * This flag indicates that the window that received this motion event is partly * or wholly obscured by another visible window above it. This flag is set to true * even if the event did not directly pass through the obscured area. * A security sensitive application can check this flag to identify situations in which * a malicious application may have covered up part of its content for the purpose * of misleading the user or hijacking touches. An appropriate response might be * to drop the suspect touches or to take additional precautions to confirm the user's * actual intent. */ public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
从上面的代码注释可以看出来,这个View不能被其他的window遮挡住,这是谷歌的一个安全策略,避免被恶意程序误导用户或劫持触摸。
第二处handle的改变是在172行
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... 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; } ...
很明显handled的值又跟mFirstTouchTarget、alreadyDispatchedToNewTouchTarget这两个值有关,另外还跟dispatchTransformedTouchEvent()这个方法有关,dispatchTransformedTouchEvent()方法,我们留在后面分析,我们先来看看这两个值是在什么时候在哪里被改变的。
mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //给mFirstTouchTarget赋值,该事件已经被子View确认处理了 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true;
这个是第145行的代码,这里是找到处理事件的子View后,做的赋值,addTouchTarget这个方法里面会对
mFirstTouchTarget赋值。
好了,如果是这样,我们再从上面的第13行开始分析。
// 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 // due to an app switch, ANR, or some other state change.//在新的事件开始(即是新的ACTION_DOWN事件),需要清除掉之前的状态以及设置mFirstTouchTarget=null; cancelAndClearTouchTargets(ev); resetTouchState(); }
这里先对该事件进行判断,如果是ACTION_DOWN事件会进到这个方法里面,做一些处理。我们来看下这两个方法都做了哪些。
/** * Cancels and clears all touch targets. */ private void cancelAndClearTouchTargets(MotionEvent event) { if (mFirstTouchTarget != null) { boolean syntheticEvent = false; //假如event为null,重新实例一个取消(MotionEvent)的事件 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); } //重置mFirstTouchTarget clearTouchTargets(); if (syntheticEvent) { event.recycle(); } } }
从这个方法的名字可以看出来,这个方法做了两件事取消跟清除TouchTarget,首先是取消,这里的取消是指分发ACTION_CANCEL事件,在我上面注释代码的第18行,dispatchTransformedTouchEvent()这个方法的第二个参数为true,这个值会在更改事件为ACTION_CANCEL,并分发给上次处理事件的View。这个分发事件的方法,我们留在后面分析,现在继续分析清除。
/** * Clears all touch targets. */ private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } }
这个方法很简单了,就对TouchTarget的next是回收,最后再把mFirstTouchTarget置null。好了,这两个方法分析完,我们再回到刚刚的那个地方,看到还有一个方法resetTouchState()
/** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; }
这个方法除了clearTouchTargets()、resetCancelNextUpFlag()这两个方法外,还对 mGroupFlags 这个标志做一个拦截方面的修改,这个标志可以让子View请求父布局不要去拦截某个事件(ACTION_DOWN除外),并且可通过getParent().requestDisallowInterceptTouchEvent()去修改这个值。
// Check for interception. final boolean intercepted; //子View唯一一个可以用来控制父类事件传递 //只有ACTION_DOWN事件跟mFirstTouchTarget不为空的情况,后面的讨论大多是围绕着mFirstTouchTarget来进行的 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //是否拦截事件,disallowIntercept为true是不拦截,false是拦截 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //一般重写onInterceptTouchEvent方法 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; }
这里有个判断,只有ACTION_DOWN以及mFirstTouchTarget不为的空的情况下,才允许进入。我们来先说下,什么时候mFirstTouchTarget会不为空,我这边先简单说下,后面代码会提及;mFirstTouchTarget是在这个事件被所在的子View消费了,这个值才不会空,即使是本身ViewGroup消费了,这个值也是为空。按照这个思路的话,大家估计也不难理解我上面说的子View可以请求父布局对ACTION_DOWN以外的事件不做拦截,另外还有一点就是,一般重写只针对onInterceptTouchEvent这个方法,而dispatchTouchEvent这个方法倒是很少重写。像我们经常遇到的ViewPager跟ScrollView这个横竖滑动冲突的问题,你们去看这两个控件源码,就可以看到都是重写了onInterceptTouchEvent这个方法。
我们回到我上面提供的源码注解中,执行上述判断后,如果canceled跟intercepted都为false的话,并且这个事件为ACTION_DOWN事件,接下来将寻找满足消费条件的子View。我们来看下,是按照什么顺序来寻找View的。
按照我上面提供源码走下来,在87行处有着下面这个方法,这个方法主要是将子View按照Z轴的大小排序。
ArrayList<View> buildOrderedChildList() { final int childrenCount = mChildrenCount; if (childrenCount <= 1 || !hasChildWithZ()) return null; if (mPreSortedChildren == null) { mPreSortedChildren = new ArrayList<>(childrenCount); } else { // callers should clear, so clear shouldn't be necessary, but for safety... mPreSortedChildren.clear(); mPreSortedChildren.ensureCapacity(childrenCount); } //自定义View排序 final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = 0; i < childrenCount; i++) { // add next child (in child order) to end of list final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View nextChild = mChildren[childIndex]; final float currentZ = nextChild.getZ(); // insert ahead of any Views with greater Z int insertIndex = i; //有点类似于插入排序,按Z轴从小到大排序 while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { insertIndex--; } mPreSortedChildren.add(insertIndex, nextChild); } return mPreSortedChildren; }
其中getAndVerifyPreorderedIndex只是对View的下标进行再次确定。这里面提到一个自定义排序的问题,正常情况的布局排序是根据xml的顺序或者addView的顺序决定的。当然google也提供了setChildrenDrawingOrderEnabled(),getChildDrawingOrder()这两个方法进行自定义排序,有需求的可以去自行了解下,我们就不深入探讨了。
if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; }
现在是取到了所有的子View,那么接下来就是筛选哪些View可以处理了。首先是先获取到哪个是获取焦点的View,并且这个View是否在这些子View里面。如果找到了就走到下一步。
//判断这个View是否具备处理的条件if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue;}
我们来看看第一个判断方法
/** * Returns true if a child view can receive pointer events. * @hide */ private static boolean canViewReceivePointerEvents(@NonNull View child) { return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null; }
第二个方法
/** * Returns true if a child view contains the specified point when transformed * into its coordinate space. * Child must not be null. * @hide */protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) { final float[] point = getTempPoint(); point[0] = x; point[1] = y; transformPointToViewLocal(point, child); final boolean isInView = child.pointInView(point[0], point[1]); if (isInView && outLocalPoint != null) { outLocalPoint.set(point[0], point[1]); } return isInView;}
可见或者是正在执行动画的,并且位置是落在这个View的范围的。满足这些条件外,再判断这个View是否已经是在mFirstTouchTarget的子View里面了,如果是的话,也是结束循环了。
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;}
以上条件都满足的话,我们就进行分发事件的方法,我们来看下这个方法做了什么操作。
/** * 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) { 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; }
这个方法一看就有点长了,慌不慌~其实这个方法就做了两件事,第一件事,就是如果cancel为true的话,更改这个事件为ACTION_CANCEL;第二件事,就是child为null的话,调用super.dispatchTouchEvent(event);child不为空的话,就调用super.dispatchTouchEvent(event);好吧,其实这个方法,只需要看上面那部分就差不多了。
...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; }...
精简版的分发~~~
如果dispatchTransformedTouchEvent方法返回true的话,就代表了这个事件已经被子View消费了,接下来关键的方法就是调用addTouchTarget()这个方法,给mFirstTouchTarget赋值。
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */ private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
如果dispatchTransformedTouchEvent方法返回false的话,那么就代表这个事件没有View消费,那就是只能自己消费了
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, ouchTarget.ALL_POINTER_IDS); }
其实到这里,整个ACTION_DOWN事件的传递就结束了。我们来做了小结,当有触摸事件传递过来时
1、先对当前设备状态进行判断,是否没被遮挡
2、紧接着如果是ACTION_DOWN事件的话,就清除状态
3、如果onInterceptTouchEvent返回true,则事件交给自己处理
3、如果是ACTION_DOWN事件的话,先去寻找获得焦点的View,如果找到了,就分发给View去处理;如果找不到就交给自己处理。
接着我们再来说下除了ACTION_DOWN以外的事件传递情况,从上面的demo我们可以得知,消费了ACTION_DOWN事件,后续的事件也将给这个View消费。也即是mFirstTouchTarget != null的情况。
// Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. //处理除了ACTION_DOWN以外的事件 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; //alreadyDispatchedToNewTouchTarget为true的话,说明已经被消费了 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //如果这个事件被拦截了,intercepted为true 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; } }
上面代码的第9行,这一块的的判断我们可以回溯到之前的mFirstTouchTarget赋值,也即是addTouchTarget()这个方法。可以发现,上述的判断如果为true,说明这个事件已经被消费了,所以handled就为true了。
上面代码的第12行,如果intercepted为true的话,那cancelChild也就为true了。而dispatchTransformedTouchEvent()上面已经分析过,cancelChild为true,会向之前消费事件的View发送ACTION_CANCEL事件。后面再把mFirstTouchTarget置成next,也即是null,那么接下来的事件将被本身给消费掉。这也验证了我们上面的demo。当然,大家也可以多做几个例子好好理解理解。
下面是整个dispatchTouchEvent()里面关键方法的调用流程,可以方便理解。
好了,整个ViewGroup层dispatchTouchEvent传递到View层的dispatchTouchEvent或者传递给super.dispatchTouchEvent(event),下一节将对View层的源码进行解析。
- Android 事件分发机制源码攻略(二) —— ViewGroup篇
- Android View 事件分发机制 源码解析(ViewGroup篇)
- Android事件分发机制(ViewGroup篇)
- Android事件分发机制源码解析(二)-ViewGroup的事件分发机制
- 探索Android事件分发机制,带你通过源码分析事件分发流程——ViewGroup篇
- Android 事件分发机制源码攻略(三) —— View篇
- Android View、ViewGroup 事件分发机制(二)
- Android事件分发机制--ViewGroup(二)
- Android事件分发机制--ViewGroup(二)
- Android事件分发机制--ViewGroup(二)
- Android View 事件分发机制源码详解(ViewGroup篇)
- Android事件分发机制源码分析之ViewGroup篇
- Android事件分发机制源码畅游解析(ViewGroup篇)
- Android View事件分发机制 二(ViewGroup)
- Android5.1 触摸屏事件分发机制和源码解析二 --(ViewGroup篇)
- Aandroid 事件分发机制(二):ViewGroup
- ViewGroup事件分发机制(源码)
- 笔记:事件分发机制(二):ViewGroup的事件分发
- DescriptionResource Path Location Type web.xml is missing and <failOnMissingWebXml> is set to true
- 栈-----noip2011 选择客栈
- Kappa:比Lambda更好更灵活的实时处理架构
- Python中的lambda
- CCS中的GEL语言
- Android 事件分发机制源码攻略(二) —— ViewGroup篇
- vue项目中weChat的title问题
- -----hdu 5073-Galaxy
- BootStrap的modal模态框的使用
- Pandas学习
- metasploit学习1
- CCS中的文注释乱码问题
- hdu3374 String Problem (字符串最小表示)
- 【安全牛学习笔记】DNS信息收集-DIG