基本知识
三个相关主体
Activity
ViewGroup
View
他们三个的嵌套关系一般是这样:
但是还要明白的是:
- ViewGroup当然可以嵌套ViewGroup,即ViewGroup也可以是另一个ViewGroup的子View。
- ViewGroup其实是继承于View,是View的子类。
事件分发机制相关三个经典函数
- dispatchTouchEvent():分发函数
- onInterceptTouchEvent():拦截函数
- onTouchEvent():消费函数
它们的功能和它们名字一样。其中拦截函数是ViewGroup独有的,其它两个函数在上面说的三个主体都存在。
事件分发机制四个经典事件
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
- ACTION_CANCLE
意如其名
事件分发机制场景
参考blog
不拦截、不消费
当三个主体的任何函数的 返回值 都不做任何处理时,即不拦截、不消费:
可见:
- 对于down事件:我会从外层一层层地分发下去(Activity->ViewGroup->view),看看
- down不消费,move,up我就不传递去了
ViewGroup拦截、无消费
当在ViewGroup使onInterceptTouchEvent()返回true,即ViewGroup对事件进行拦截时:
可见:
ViewGoup消费,不拦截
当在ViewGroup使onTouchEvent()返回true,即ViewGroup对事件进行消费时:
可见:
- 当down被消费了就不会往上冒
- move up不会往下发,而是直接分发给消费者。
源码分析
具体代码怎么实现?主要是看分发函数dispatchTouchEvent(),接下来我们看看 三个主体的dispatchTouchEvent() 源码分析
Activity的dispatchTouchEvent()源码
1 2 3 4 5 6 7 8 9 10 11 12 13
| public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } 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
之后得出一些小结,现在贴出一部分,一段一段分析。
总体逻辑分析
我把部分代码和具体逻辑去掉,看它的空架子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| private TouchTarget mFirstTouchTarget;
public boolean dispatchTouchEvent(MotionEvent ev) { ... if (onFilterTouchEventForSecurity(ev)) { if (actionMasked == MotionEvent.ACTION_DOWN) {} final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {} else {} final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { if (newTouchTarget == null && childrenCount != 0) { } if (newTouchTarget == null && mFirstTouchTarget != null) { } } } if (mFirstTouchTarget == null) {} else {} } ... return handled; }
|
关于解析看代码中的注释。接下来看看其中几段逻辑具体怎么实现的。
具体分析一
看一下分发给DOWN给子View的具体逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| if (newTouchTarget == null && childrenCount != 0) { 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 (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { 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; } } }
|
这里我只贴出部分的代码,解析都在注释,可以看到,mFirstTouchTarget链表只存在一个值,就是响应了事件的那个child。
具体分析二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { 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; } }
|
View的dispatchTouchEvent()源码
同样的,我省略了部分代码,解析看注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } 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的分发函数,用类似于递归的方式提问和接收应答。
- 对于较大量的信息,命令传送,可以先派一个小兵嗅探一下,记录可行的路线,后续信息按路线分发。