大领导又给小明安排任务——Android触摸事件
这是Android触摸事件系列的第二篇,系列文章目录如下:
- 大领导给小明安排任务——Android触摸事件
- 大领导又给小明安排任务——Android触摸事件
把上一篇中领导分配任务的故事,延展一下:
大领导安排任务会经历一个“递”的过程:大领导先把任务告诉小领导,小领导再把任务告诉小明。也可能会经历一个“归”的过程:小明告诉小领导做不了,小领导告诉大领导任务完不成。而后,就没有而后了。。。。但假如这次完成了任务,大领导还会继续将后序任务分配给小明。
故事的延展部分和今天要讲的ACTION_DONW
后序事件很相似,先来答复上一篇中遗留的另一个问题“阻拦事件”:
阻拦事件
ViewGroup
在遍历孩子分发触摸事件前还有一段阻拦逻辑:
public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override public boolean dispatchTouchEvent(MotionEvent ev) { ... // Check for interception. //检查ViewGroup能否要阻拦触摸事件的下发 final boolean intercepted; //第一个条件表示阻拦ACTION_DOWN事件 //第二个条件表示阻拦ACTION_DOWN事件已经分发给孩子,现在阻拦后序事件 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; } ... //当事件没有被阻拦的时候,将其分发给孩子 if (!canceled && !intercepted) { //遍历孩子并将事件分发给它们 //假如有孩子宣称要消费事件,则将其增加到触摸链上 //这段逻辑在上一篇中分析过,这里就省略了 } } //将触摸事件分发给触摸链 if (mFirstTouchTarget == null) { //没有触摸链 //假如事件被ViewGroup阻拦,则触摸链为空,ViewGroup自己消费事件 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... } } //返回true表示阻拦事件,默认返回false public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) { ... if (child == null) { //ViewGroup孩子都不愿意接收触摸事件或者者触摸事件被阻拦 则其将自己当成View解决(调用View.dispatchTouchEvent()) handled = super.dispatchTouchEvent(transformedEvent); } ... }}
当允许阻拦时,onInterceptTouchEvent()
会被调用,假如重载这个方法并且返回true
,表示ViewGroup
要对事件进行阻拦,此时不再将事件分发给孩子而是自己消费(通过调用View.dispatchTouchEvent()
最终走到ViewGroup.onTouchEvent()
)。
用一张图总结一下:
图1
- 图中黑色的箭头表示触摸事件传递的路径,灰色的箭头表示触摸事件消费的回溯路径。
onInterceptTouchEvent()
返回true
,导致onTouchEvent()
被调用,由于onTouchEvent()
返回true
,导致dispatchTouchEvent()
返回true
。 - 精确的说,阻拦触摸事件的受益者是所有上层的
ViewGroup
(包括自己),由于触摸事件不再会向下层的View
传递。
ACTION_MOVE和ACTION_UP
上一篇在阅读源码的时候,埋下了一个伏笔,现在将其补全:
public abstract class ViewGroup extends View implements ViewParent, ViewManager { //触摸链头结点 private TouchTarget mFirstTouchTarget; ... @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (!canceled && !intercepted) { ... //当ACTION_DOWN的时候才遍历寻觅消费触摸事件的孩子,若找到则将其加入到触摸链 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //遍历孩子 for (int i = childrenCount - 1; i >= 0; i--) { ... //转换触摸坐标并分发给孩子(child参数不为null) if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... //有孩子愿意消费触摸事件,将其插入“触摸链” newTouchTarget = addTouchTarget(child, idBitsToAssign); //表示已经将触摸事件分发给新的触摸目标 alreadyDispatchedToNewTouchTarget = true; break; } ... } } } if (mFirstTouchTarget == null) { //假如没有孩子愿意消费触摸事件,则自己消费(child参数为null) handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } //触摸链不为null,表示有孩子消费了ACTION_DOWN else { //将伏笔补全 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; //遍历触摸链将ACTION_DOWN的后序事件分发给孩子 while (target != null) { final TouchTarget next = target.next; //上一篇分析了,ACTION_DOWN会走这里 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { //假如已经将触摸事件分发给新的触摸目标,则返回true handled = true; } //ACTION_DONW的后序事件走这里 else { ... //将触摸事件分发给触摸链上的触摸目标 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } ... } predecessor = target; target = next; } } ... if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //假如是ACTION_UP事件,则将触摸链清空 resetTouchState(); } return handled; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; ... // Perform any necessary transformations and dispatch. //进行必要的坐标转换而后分发触摸事件 if (child == null) { //ViewGroup孩子都不愿意消费触摸事件 则其将自己当成View解决(调用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()); } //将触摸事件分发给孩子 handled = child.dispatchTouchEvent(transformedEvent); } ... return handled; } /** * Resets all touch state in preparation for a new cycle. * 重置Touch标志 */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; } /** * 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; } }}
触摸事件是一个序列,序列总是以ACTION_DOWN
开始,紧接着有ACTION_MOVE
和ACTION_UP
。当ACTION_DOWN
发生时,ViewGroup.dispatchTouchEvent()
会将愿意消费触摸事件的孩子存储在触摸链中,当后序事件会分发给触摸链上的对象。
用一种图总结一下:
图2
- 图中黑色箭头表示
ACTION_DOWN
事件的传递路径,灰色箭头表示ACTION_MOVE
和ACTION_UP
事件的传递路径。即只需有视图宣称消费ACTION_DOWN
,则其后序事件也传递给它,不论它能否宣称消费ACTION_MOVE
和ACTION_UP
,假如它不消费,则后序事件会像上一篇分析的ACTION_DOWN
一样向上回溯给上层消费。
图3
- 图中黑色箭头表示
ACTION_DOWN
事件的传递路径,灰色箭头表示ACTION_MOVE
和ACTION_UP
事件的传递路径。即所有视图都不消费ACTION_DOWN
,则其后序事件只会传递给Activity.onTouchEvent()
。
ACTION_CANCEL
把领导布置任务的故事继续延展一下:大领导给小领导布置了任务1,小领导把他传递给小明,小明完成了。紧接着大领导给小领导布置了任务2,小领导决定自己解决任务2,于是他和小明说后序任务我来接手,你可以忙别的事情。
故事对应的触摸事件传递场景是:Activity
将ACTION_DOWN
传递给ViewGroup
,ViewGroup
将其传递给View
,View
宣称消费ACTION_DOWN
。Activity
继续将ACTION_MOVE
传递给ViewGroup
,但ViewGroup
对其做了阻拦,此时ViewGroup
会发送ACTION_CANCEL
事件给View
。
看下源码:
public abstract class ViewGroup extends View implements ViewParent, ViewManager { public boolean dispatchTouchEvent(MotionEvent ev) { //检查ViewGroup能否要阻拦触摸事件的下发 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; } } ... //假如孩子消费ACTION_DOWN事件,则会在这里将其增加到触摸链中 if (!canceled && !intercepted) { ... } //将触摸事件分发给触摸链 if (mFirstTouchTarget == null) { //没有触摸链 表示当前ViewGroup中没有孩子愿意接收触摸事件 //将触摸事件分发给自己 } 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 { //假如事件被阻拦则cancelChild为true final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //将ACTION_CANCEL事件传递给孩子 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } //假如发送了ACTION_CANCEL事件,将孩子从触摸链上摘除 if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } ... } 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 { //将ACTION_CANCEL事件传递给孩子 handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ...}
当孩子消费了ACTION_DOWN
事件,它的引用被会保存在父亲的触摸链中。当父亲阻拦后序事件时,父亲会向触摸链上的孩子发送ACTION_CANCEL
事件,并将孩子从触摸链上摘除。后序事件就传递到父亲为止。
总结
经过两篇文章的分析,对Android触摸事件的分发有了初步的理解,得出了以下结论:
Activity
接收到触摸事件后,会传递给PhoneWindow
,再传递给DecorView
,由DecorView
调用ViewGroup.dispatchTouchEvent()
自顶向下分发ACTION_DOWN
触摸事件。ACTION_DOWN
事件通过ViewGroup.dispatchTouchEvent()
从DecorView
经过若干个ViewGroup
层层传递下去,最终到达View
。- 每个层次都可以通过在
onTouchEvent()
或者OnTouchListener.onTouch()
返回true
,来告诉自己的父控件触摸事件被消费。只有当下层控件不消费触摸事件时,其父控件才有机会自己消费。 - 触摸事件的传递是从根视图自顶向下“递”的过程,触摸事件的消费是自下而上“归”的过程。
ACTION_MOVE
和ACTION_UP
会沿着刚才ACTION_DOWN
的传递路径,传递给消费了ACTION_DOWN
的控件,假如该控件没有公告消费这些后序事件,则它们也像ACTION_DOWN
一样会向上回溯让其父控件消费。- 父控件可以通过在
onInterceptTouchEvent()
返回true
来阻拦事件向其孩子传递。假如在孩子已经消费了ACTION_DOWN
事情后才进行阻拦,父控件会发送ACTION_CANCEL
给孩子。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 大领导又给小明安排任务——Android触摸事件