文章目录
基本知识
事件传递的三个主体
Activity、ViewGroup、View
他们三个的嵌套关系通常是这样:
可是还要明白的是:
java
- ViewGroup固然能够嵌套ViewGroup,即ViewGroup也能够是另外一个ViewGroup的子View。
- ViewGroup实际上是继承于View,是View的子类。
事件分发机制相关三个经典函数
- dispatchTouchEvent():分发函数
- onInterceptTouchEvent():拦截函数
- onTouchEvent():消费函数
它们的功能和它们名字同样。其中拦截函数是ViewGroup独有的,其它两个函数在上面说的三个主体都存在。函数
事件分发机制四个经典事件
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
- ACTION_CANCLE
意如其名源码分析
事件分发机制场景
不拦截、不消费
当三个主体的任何函数的 返回值 都不作任何处理时,即不拦截、不消费:动画
可见:
ui
- 对于down事件:我会从外层一层层地分发下去(Activity->ViewGroup->view),看看
- down不消费,move,up我就不传递去了
ViewGroup拦截、无消费
当在ViewGroup使onInterceptTouchEvent()返回true,即ViewGroup对事件进行拦截时:this
可见:
spa
- 事件被拦截以后就不会往下分发
ViewGoup消费,不拦截
当在ViewGroup使onTouchEvent()返回true,即ViewGroup对事件进行消费时:
可见:
.net
- 当down被消费了就不会往上冒
- move up不会往下发,而是直接分发给消费者。
源码分析
具体代码怎么实现?主要是看分发函数dispatchTouchEvent(),接下来咱们看看 三个主体的dispatchTouchEvent() 源码分析code
Activity的dispatchTouchEvent()源码
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();//这是一个空方法 } //主要看这一句 //getWindow().superDispatchTouchEvent(ev) //这句函数调用的时DecorView的superDispatchTouchEvent() //而DecorView继承于ViewGroup if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
代码中我写了注释,咱们能够得出下面的结论:
- getWindow().superDispatchTouchEvent(ev),实际是调用了一个ViewGroup的dispatchTouchEvent()
- getWindow().superDispatchTouchEvent(ev)返回true,说明有子View消费该事件(为何呢?咱们要分析完ViewGroup的dispatchTouchEvent()才知道,但如今能够暂时给出这个结论);这个子View多是某个ViewGroup或者View。
- 若是有子view消费该事件则返回true,不然调用自身的onTouchEvent(ev),即把事件分发给本身。
ViewGroup的dispatchTouchEvent()源码
ViewGroup的dispatchTouchEvent()源码很长,我参考了https://blog.csdn.net/wolinxuebin/article/details/53057075
以后得出一些小结,如今贴出一部分,一段一段分析。
整体逻辑分析
我把部分代码和具体逻辑去掉,看它的空架子
//这是一个单链表,我暂时理解为用于存放响应了DOWN的事件 private TouchTarget mFirstTouchTarget; public boolean dispatchTouchEvent(MotionEvent ev) { ... //判断是不是模糊窗口,若是是窗口,则表示不但愿处理改事件。(如dialog后的窗口) if (onFilterTouchEventForSecurity(ev)) { // 清空以前的状态 if (actionMasked == MotionEvent.ACTION_DOWN) { } //检查是否须要拦截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { } else { } //检查是否要取消,即标记了PFLAG_CANCEL_NEXT_UP_EVENT 或者 当前是一个Cancel事件 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; //不用取消,无需拦截,则进行事件分发 if (!canceled && !intercepted) { //分发DOWN事件 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //分发DOWN给child if (newTouchTarget == null && childrenCount != 0) { } //若是没有child相应该事件,则将此事件交给最近加入的target? //这里不是很懂,若是没有child响应,那么mFirstTouchTarget也是null呀 if (newTouchTarget == null && mFirstTouchTarget != null) { } } } //mFirstTouchTarget为空代表没有child响应这个事件,则分发给本身 if (mFirstTouchTarget == null) { } //按照mFirstTouchTarget分发 else { } } ... //若是自身或者child消费了事件则返回true,不然返回false return handled; }
关于解析看代码中的注释。接下来看看其中几段逻辑具体怎么实现的。
具体分析一
看一下分发给DOWN给子View的具体逻辑:
//分发DOWN给child if (newTouchTarget == null && childrenCount != 0) { // 对子Views进行排序,有两种方式:一、按照Z轴,二、按照draw final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //遍历子View for (int i = childrenCount - 1; i >= 0; i--) { //这里两行代码,简单的理解根据不一样的排列选项(一、view添加到 二、view的draw顺序 三、viewZ 轴顺序) final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //canViewReceivePointerEvents 判断child是否为visiable 或者 是否有动画 //isTransformedTouchPointInView 判断x, y是否在view的区域内(若是是执行了补间动画 则x,y会经过获取的matrix变换值 // 换算当相应的区域,这也是为何补间动画的触发区域不随着动画而改变) if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } //若是chile已经在mFirstTouchTarget单链表里面,结束循环 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; } //判断child的dispatchTouchEvent()是否会返回true,若是是true,将child加入单链表,而后结束循环 //dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)会调用child的dispatchTouchEvent() resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // 找到childIndex所表明的child的最原始的index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } //将相应该事件的child包装成一个Target,添加到mFirstTouchTarget链表中 mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } }
这里我只贴出部分的代码,解析都在注释,能够看到,mFirstTouchTarget链表只存在一个值,就是响应了事件的那个child。
具体分析二
//这一段的要么分发down给本身要么按照单链表分发move、up //伪代码 // if (mFirstTouchTarget == null) { //没有child响应事件,则分发给本身,handled将做为返回值返回。 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; //在前面Down事件处理中,已经将这个事件交给newTouchTarget处理过了,就不重复处理了 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //这里实际上是分发move和up事件,由于down事件在前面已经处理完了,不会进入这里 //再次断定是否须要cancel,由于有可能在move过程事件被拦截 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } //若是cancel,回收链表节点空间,最后使mFirstTouchTarget置null if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
View的dispatchTouchEvent()源码
一样的,我省略了部分代码,解析看注释
public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (onFilterTouchEventForSecurity(event)) { //若是设置了onTouchListener,会先调用onTouch() ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //调用onTouchEvent() if (!result && onTouchEvent(event)) { result = true; } } return result; }
能够看到:
- View的dispatchTouchEvent()的返回值取决于onTouch()或onTouchEvent(),也就是有没有消费该事件。
- 若是onTouch()返回true的话就不会调用onTouchEvent(),这就是为何有些博客写onTouch()优先于onTouchEvent()。
总结
分析源码就能够知道前面说的三种场景是怎么回事了。
有点像皇帝派任务,皇帝说如今有个好活儿,可是不知道谁要接这个活儿,因而派了一个小太监去探测一下;
小太监先去找宰相,宰相又让他去找知府,知府让他去找衙门小兵。
这里面皇帝就像Acticity、各级官员就像ViewGroup、小兵就像View、而小太监就像DOWN事件,活儿就是跟着DOWN后面的MOVE和UP事件。
- 不拦截、不消费:小太监一层层找到小兵后,没有一个小兵想接这个活儿(一层层分发DOWN事件),因而小兵沿路返回报告给知府、知府也不想作就报告给宰相,宰相不想作就回去报告给皇帝,皇帝说没人作那我看看本身能不能作吧(DOWN事件回到Activity派给本身,MOVE、UP也再也不分发而是直接派给本身)
- ViewGroup拦截、不消费:宰相让小太监找到知府的时候, 这个知府有点霸道直接把小太监拦下了,小太监就就再也不继续通知下级人员了(拦截事件)。可是呢这个官员只是单纯拦下了小太监但他并不想接这个活儿,因而小兵仍是沿路回去报告给说下面没人接活,最终仍是传回给皇帝说没人作那我看看本身能不能作(MOVE、UP再也不分发直接派给本身)
- ViewGroup消费、不拦截:一样,无人拦截的话,小太监一层层找到小兵,发现小兵没人想作,就回去报告知府,这时候知府说小兵不作我来作(DOWM在这里被消费了)。而后知府写信报告宰相说这活儿我接了(返回true),宰相又报告皇帝说下面有人接受任务了,然会皇帝下次就直接派发任务给宰相,宰相找到那个愿意接受任务知府,把任务派给他。(派发MOVE、UP)
分发函数分发DOWN时其实有点相似于递归的方式,只不过不是本身调用本身,而是一层层地调用child的同名函数。分发MOVE、UP则再也不一一询问,而是根据DOWN是否被消费进行分发。
源码很长如今头都有点乱,那么从源码能够学习到什么呢,你们帮忙补充吧
- 若有有这种嵌套式的应答需求,能够学习ViewGroup的分发函数,用相似于递归的方式提问和接收应答。
- 对于较大量的信息,命令传送,能够先派一个小兵嗅探一下,记录可行的路线,后续信息按路线分发。