Andorid 中TouchEvent理解(二) TouchEvent分发机制(onTouchEvent()回调返回值区别)
2017-06-19 16:06
441 查看
逻辑较为复杂,分两步来分析,相对好一些。
1)Down事件确定TouchEvent处理的View(mFirstTouchTarget)
2)将MOVE,UP事件传递到第一步中确定的时间处理View(mFirstTouchTarget)下面分别分析这两步怎么做的。
分析背景,当前一个LinerLayout中加入一个自定义的chlidView。
第一步:Down事件确定mFirstTouchTarget
上一篇分析到DecorView调用ViewGroup的dispatchTouchEvent() 。对于触摸事件来说第一件Event必然是Down事件,分析这个流程就是第一步了,看一下源码对这个流程的处理。
1)初始化ViewGroup的传递状态,这里即是一个完整事件传递的起始位置。
这段代码判断当前为ACTION_DOWN执行的方法,
cancelAndClearTouchTargets(ev):将mFirstTouchTarget赋值为null,这个就是要找寻的处理事件的View,初始化为null。
resetTouchState():强制清除mGroupFlags中的FLAG_DISALLOW_INTERCEPT,这个Flag,这个flag影响到拦截函数onInterceptTouchEvent()的调用。
2)是否拦截事件,onTouchEvent在这里调用
这里即使用了上一步的FLAG_DISALLOW_INTERCEPT这个flag做了判断是否要调用拦截函数,根据这里的判断条件,执行拦截函数返回false不拦截,intercept为false。
3)找到Down事件的落点位置的ChlidView并传递Down事件,
这段代码校多,在这段代码中有循环遍历ChlidView的代码,简单来讲就是
a.使用isTransformedTouchPointInView()来找到落点的位置在哪一个View的范围内,在如下的代码中传递到ChlidView,
这个函数的作用简单来讲如果child为null,将使用View的dispatchTouchEvent(),将自己作为View来使用,即递归的边界,调用onTouchEvent()。现在找到了这个ChildView不为null,传递到这个View调用dispatchTouchEvent(),接着目标View使用onTouchEvent():这里可以返回true和false,接下来就是这两个返回值的区别了。
先分析true的情况下:赋值当前的mFirstTouchTarget为找到的chlidView.
addTouchTarger()完成了赋值,同时标志位alreadyDispatchedToNewTouchTarget 也置为true。
false情况下
mFirstTouchTarget继续为null,alreadyDispatchedToNewTouchTarget 继续为false
第二步:根据mFirstTouchTarget传递ACTION_MOVE,ACTION_UP
在上一步中,Down事件传递了下去,不管Chlid的TouchEvent的返回值是如何,都完成了整个传递的流程的,但是,如果是MOVE事件的话,第一步的代码是不会走的,跳过之后来到这里(上面这一段代码),Down事件的时候被alreadyDispatchedToNewTouchTarget 这个标记为过滤掉了,因此这段代码中只处理MOVE和UP。
第一步中已经知道onTouchEvent的返回值true时,mFirstTouchTarget为目标View,onTouchEvent的返回值false时,mFirstTouchTarget为null,
代码中有判断mFirstTouchTarget!=null的时候才执行dispatchTransformedTouchEvent,这就是当ChildView的Ontouch返回false时,只能收到一个Down事件的原因。
onTouchEvent()返回为false时,通俗的讲:找到了Down落点的位置View但是这个View不处理,那么就是没找到,自己来处理,自己的OnTouchEvent()再不处理,就又回到了调用这个View的上一级View处理,和此处递归逻辑一致,只是到了上一层。最上是Activity的OntouchEvent()回调,这里返回值没有任何效果,不处理也得处理了。
1)Down事件确定TouchEvent处理的View(mFirstTouchTarget)
2)将MOVE,UP事件传递到第一步中确定的时间处理View(mFirstTouchTarget)下面分别分析这两步怎么做的。
分析背景,当前一个LinerLayout中加入一个自定义的chlidView。
第一步:Down事件确定mFirstTouchTarget
上一篇分析到DecorView调用ViewGroup的dispatchTouchEvent() 。对于触摸事件来说第一件Event必然是Down事件,分析这个流程就是第一步了,看一下源码对这个流程的处理。
1)初始化ViewGroup的传递状态,这里即是一个完整事件传递的起始位置。
// 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(); }
这段代码判断当前为ACTION_DOWN执行的方法,
cancelAndClearTouchTargets(ev):将mFirstTouchTarget赋值为null,这个就是要找寻的处理事件的View,初始化为null。
resetTouchState():强制清除mGroupFlags中的FLAG_DISALLOW_INTERCEPT,这个Flag,这个flag影响到拦截函数onInterceptTouchEvent()的调用。
2)是否拦截事件,onTouchEvent在这里调用
final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 4000 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; }
这里即使用了上一步的FLAG_DISALLOW_INTERCEPT这个flag做了判断是否要调用拦截函数,根据这里的判断条件,执行拦截函数返回false不拦截,intercept为false。
3)找到Down事件的落点位置的ChlidView并传递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. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); 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; } 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; } }
这段代码校多,在这段代码中有循环遍历ChlidView的代码,简单来讲就是
a.使用isTransformedTouchPointInView()来找到落点的位置在哪一个View的范围内,在如下的代码中传递到ChlidView,
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
这个函数的作用简单来讲如果child为null,将使用View的dispatchTouchEvent(),将自己作为View来使用,即递归的边界,调用onTouchEvent()。现在找到了这个ChildView不为null,传递到这个View调用dispatchTouchEvent(),接着目标View使用onTouchEvent():这里可以返回true和false,接下来就是这两个返回值的区别了。
先分析true的情况下:赋值当前的mFirstTouchTarget为找到的chlidView.
newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true;
addTouchTarger()完成了赋值,同时标志位alreadyDispatchedToNewTouchTarget 也置为true。
false情况下
mFirstTouchTarget继续为null,alreadyDispatchedToNewTouchTarget 继续为false
第二步:根据mFirstTouchTarget传递ACTION_MOVE,ACTION_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; } }
在上一步中,Down事件传递了下去,不管Chlid的TouchEvent的返回值是如何,都完成了整个传递的流程的,但是,如果是MOVE事件的话,第一步的代码是不会走的,跳过之后来到这里(上面这一段代码),Down事件的时候被alreadyDispatchedToNewTouchTarget 这个标记为过滤掉了,因此这段代码中只处理MOVE和UP。
第一步中已经知道onTouchEvent的返回值true时,mFirstTouchTarget为目标View,onTouchEvent的返回值false时,mFirstTouchTarget为null,
代码中有判断mFirstTouchTarget!=null的时候才执行dispatchTransformedTouchEvent,这就是当ChildView的Ontouch返回false时,只能收到一个Down事件的原因。
onTouchEvent()返回为false时,通俗的讲:找到了Down落点的位置View但是这个View不处理,那么就是没找到,自己来处理,自己的OnTouchEvent()再不处理,就又回到了调用这个View的上一级View处理,和此处递归逻辑一致,只是到了上一层。最上是Activity的OntouchEvent()回调,这里返回值没有任何效果,不处理也得处理了。
相关文章推荐
- Andorid 中TouchEvent理解(三) TouchEvent分发机制(onInterceptTouchEvent()回调返回值区别)
- Android事件分发机制完全解析,带你从源码的角度彻底理解dispatchTouchEvent,onInterceptTouchEvent
- 关于dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent的分发机制浅析
- android事件的分发和消费机制(onTouchEvent())
- setOnTouchEvent 设置返回值为true 和 false的区别
- setOnTouchEvent 设置返回值为true 和 false的区别
- Android进阶——Android事件分发机制之dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
- 事件分发 细说Android事件传递机制(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)
- IOS中触摸事件(touch event)的分发以及响应者链(The Responder Chain)机制理解
- 完全理解Android TouchEvent事件分发机制(一)
- 理解Android中的TouchEvent事件分发机制
- Android事件传递机制以及ViewGroup的onInterceptTouchEvent的理解
- 完全理解Android TouchEvent事件分发机制(二)
- Android 编程下 Touch 事件的分发和消费机制Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onIntercep
- [android] setOnTouchEvent 设置返回值为true 和 false的区别
- 我对onInterceptTouchEvent 和onTounEvent事件分发传递的自我理解(原创)
- 编程下 Touch 事件的分发和消费机制dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()
- Android触摸事件分发机制之requestDisallowInterceptTouchEvent
- 细说Android事件传递机制(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)
- onInterceptTouchEvent()的机制