(转载请注明做者:RubiTree,地址:blog.rubitree.com )java
事件分发,我想大部分人都能说几句,哦,三大方法,哦,那段经典伪代码,哦,责任链... 但若是要让你完完整整捋一遍,你可能就开始支支吾吾了,只能想到啥说啥git
这块的东西确实麻烦,说出来不怕吓到你,事件流到底怎么流与这些因素都有关系:是什么事件类型(DOWN/MOVE/UP/CANCEL
)、在哪一个视图层次(Activity/ViewGroup/View
)、在哪一个回调方法(dispatch()/onIntercept()/onTouch()
)、回调方法给不一样的返回值(true/false/super.xxx
),甚至对当前事件的不一样处理还会对同一事件流中接下来的事件形成不一样影响github
好比我能够问:重写某个ViewGroup
里的dispatchTouchEvent
方法,对MOVE
事件返回false
,整个事件分发过程会是什么样的?微信
因而就有人对这些状况分门别类进行总结,获得了不少规律,也画出了纷繁复杂的事件分发流程图:app
甚至还有相似题图那样的动态流程图 (是的,吸引你进来的题图竟然是反面教材,我也很心疼啊,画了我半个下午,结果并无太大的帮助)框架
这些规律和流程图确实是对的,并且某种意义上也是很是清晰的,能帮助你在调试 Bug 的时候找到一点方向。 你或许能够奋发图强,把这些流程图和规律背下来,也能在须要的时候一通叽里呱啦背完你们大眼瞪小眼。 但它们并不能让你真正理解事件分发是什么样子,你可能某一次花费了大量的时间去看懂它们,可是「每次都能看明白!过一段时间又忘了!」 (某段有表明性的评论原话)ide
但讲道理,分发个触摸事件为何会这么复杂呢?须要这么复杂吗?图啥呢?布局
因而,让咱们回到起点,看看分发触摸事件究竟是为了解决一个什么样的问题,有没有更简单的分发办法?而后看看当需求增长的时候,要怎么调整这个简单的分发策略? 看到最后你就会发现,原来一切是那么地天然。post
因此,不用死记硬背,也不用急着去怼完整的事件分发流程,那么多复杂的逻辑和状况其实都是围绕着最根本的问题发展出来的,是随着需求的增长一步步变得复杂的,理解了演化过程,你天然会对其演化的结果了然于胸,想忘都忘不掉。测试
从根本问题出发,一切就会变得天然而然。
艾维巴蒂,黑喂狗! 下面我将从最简单的需求开始思考方案、编写代码,而后一步步增长需求、调整方案、继续编写代码,争取造出一个麻雀虽小五脏俱全的事件分发框架。
咱们先实现一个最简单的需求:Activity 中有一堆层层嵌套的 View,有且只有最里边那个 View 会消费事件
(黄色高亮 View 表明能够消费事件,蓝色 View 表明不消费事件)
思考方案:
Activity
链接着根ViewDecorView
,它是通往外界的桥梁,能接收到屏幕硬件发送过来的触摸事件Activity
开始,通过一层一层 ViewGroup ,传到最里边的 ViewpassEvent(ev)
方法,父亲一层层往里调,能把事件传递过去,就完成了需求示意图
麻雀代码:
(本文代码使用Kotlin编写,核心代码也提供了Java版本)
open class MView {
open fun passEvent(ev: MotionEvent) {
// do sth
}
}
class MViewGroup(private val child: MView) : MView() {
override fun passEvent(ev: MotionEvent) {
child.passEvent(ev)
}
}
复制代码
Activity
当成MViewGroup
处理也没有问题MViewGroup
继承MView
而不是反过来,由于 MView
是不须要 child
字段的而后咱们增长一条需求,让状况复杂一点:Activity
中有一堆层层嵌套的View,有好几个叠着的View能处理事件
同时须要增长一条设计原则:用户的一次操做,只能被一个View真正处理(消费)
若是使用第一次试造的框架,要遵照这条原则,就须要在每个能够处理事件的View层级,判断出本身要处理事件后,不继续调用child
的passEvent()
方法了,保证只有本身处理了事件。 但若是真这样实现了,在大部分场景下会显得怪怪的,由于处理事件的顺序不对:
因此实现新增需求的一个关键是:找到那个适合处理事件的View,而咱们经过对业务场景进行分析,获得答案是:那个最里面的View适合处理事件
这就不能是等parent
不处理事件了才把事件传给child
,应该反过来,你须要事件的处理顺序是从里向外:里边的child
不要事件了,才调用parent
的passEvent()
方法把事件传出来。 因而得加一条向外的通道,只能在这条向外的通道上处理事件,前面向里的通道什么都不干,只管把事件往里传。 因此这时你有了两条通道,改个名字吧,向里传递事件是passIn()
方法,向外传递并处理事件是passOut()
方法。
示意图
麻雀代码:
open class MView {
var parent: MView? = null
open fun passIn(ev: MotionEvent) {
passOut(ev)
}
open fun passOut(ev: MotionEvent) {
parent?.passOut(ev)
}
}
class MViewGroup(private val child: MView) : MView() {
init {
child.parent = this // 示意写法
}
override fun passIn(ev: MotionEvent) {
child.passIn(ev)
}
}
复制代码
这段代码没有问题,很是简单,可是它对需求意图的表达不够清晰,增长了框架的使用难度
passIn()
的时候只传递事件,但愿在passOut()
的时候每一个View决定是否要处理事件,并进行处理,并且在处理事件后,再也不调用parent
的passOut()
方法把事件传出来因而咱们用一个叫作dispatch()
的方法单独放事件传递的控制逻辑,用一个叫作onTouch()
的方法做为事件处理的钩子,并且钩子有一个返回值,表示钩子中是否处理了事件:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
override fun dispatch(ev: MotionEvent): Boolean {
var handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
return handled
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
复制代码
这样写完,整个行为其实没有变化,但你会发现:
dispatch()
中,一目了然onTouch()
单纯是一个钩子,框架使用者只须要关心这个钩子和它的返回值,不用太关心控制流程parent
也不须要了上文的实现看上去已经初具雏形了,但其实连开始提的那条原则都没实现完,由于原则要求一次操做只能有一个 View 进行处理,而咱们实现的是一个触摸事件只能有一个View进行处理。 这里就涉及到一次触摸操做和一个触摸事件的区别:
DOWN/UP/ING
,其中ING
有点不够专业,改个名字叫MOVE
吧DOWN
事件开始、中间是多个MOVE
事件、最后结束于UP
事件的事件流组成因而设计原则更确切地说就是:一次触摸产生的事件流,只能被一个View消费
在上次试造的基础上把一个事件变成一个组事件流,其实很是简单:处理DOWN
事件时跟前面处理一个事件时同样,但须要同时记住DOWN
事件的消费对象,后续的MOVE/UP
事件直接交给它就好了
麻雀代码:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) handled = onTouch(ev)
} else {
if (isChildNeedEvent) handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
复制代码
代码好像增长了不少,其实只多作了两件事:
isChildNeedEvent
状态,对是子View是否处理了DOWN
事件进行记录,并在其余触摸事件时使用这个状态DOWN
事件的最开始和收到UP
事件的最后,重置状态此时框架使用者仍是只须要关心onTouch()
钩子,在须要处理事件时进行处理并返回true
,其余事情框架都作好了。
上面的框架已经能完成基本的事件分发工做了,但下面这个需求,你尝试一下用如今框架能实现吗? 需求:在可滑动View中有一个可点击View,须要让用户即便按下的位置是可点击View,再进行滑动时,也能够滑动外面的的可滑动View。
假如使用上面的框架:
因此直接使用如今的模型去实现的「条目可点击的滑动列表」会永远滑动不了。
那怎么办呢?
dispatch()
方法在传入事件过程当中的人设,让它不是只能往里传递事件,而是在本身能消费事件的时候把事件给本身
直接想实现以为处处是矛盾,找不到突破口,那就从头开始吧,从什么样的触摸反馈是用户以为天然的出发,看看这种符合直觉的反馈方案是否存在,找出来它是什么,再考虑咱们要怎么实现:
DOWN
事件刚来的时候,能判断用户想干什么吗?很抱歉,不能DOWN
事件传过来的时候,判断出用户到底想作什么,因而两个View其实都不能肯定本身是否要消费事件我*,这不傻*了吗,还搞什么GUI啊,你们都用命令行吧 等等,不要着急,GUI仍是得搞的,不搞没饭吃的我跟你讲,因此你仍是得想一想,想尽办法去实现。
你先忘记前面说的原则,你想一想,不考虑其余因素,也不是只能用DOWN
事件,只要你能判断用户的想法就行,你有什么办法
DOWN
,而后MOVE
很小一段,也不会MOVE出这个子View,关键是比较短的时间就UP
DOWN
,而后开始MOVE
,这时候可能会MOVE出这个子View,也可能不,但关键是比较长的时间也没有在UP
,一直是在MOVE
DOWN
不行,还得看接下来的事件流,得走着瞧UP
,就是点击里边的ViewUP
,但没怎么MOVE
,就是长按里边的ViewMOVE
比较长的距离,就是滑动外面的View看上去这个目标 View 断定方案很不错,安排得明明白白,但咱们现有的事件处理框架实现不了这样的断定方案,至少存在如下两个冲突:
DOWN
的时候判断当前事件流是否是该给本身,因此一开始它们都只能返回false
。但为了能对后续事件作判断,你但愿事件继续流过它们,按照当前框架的逻辑,你又不能返回false
。因此要解决上述的冲突,就确定要对上一版的事件处理框架进行修改,并且看上去一不当心就会大改
dispatch()
方法在传入事件过程当中的人设,让它不是只传递事件了,还能够在往里传递事件前进行拦截,可以看状况拦截下事件并交给本身的 onTouch()
处理onTouch()
里对DOWN
事件返回true
,无论是否识别出当前属于本身的消费模式disptach
事件了,而是直接给本身的onTouch()
onIntercept()
在父 View 往里dispatch
事件前,开发者能够覆写这个方法,加入本身的事件模式分析代码,而且能够在肯定要拦截的时候进行拦截
onTouch()
示意图:
因而使用思路二,在「三造」的基础上,修改获得如下代码:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
}
复制代码
写的过程当中增长了一些对细节的处理:
DOWN
事件的dispatch()
前须要拦截,在后续事件中,也须要加入拦截,不然没法实现中途拦截的目标isSelfNeedEvent
记录本身是否拦截过事件,若是拦截过,后续事件直接就交给本身处理这一下代码是否是看上去瞬间复杂了,但其实只是增长了一个事件拦截机制,对比上一次试造的轮子,会更容易理解。(要是 Markdown 支持代码块内自定义着色就行了)
并且对于框架的使用者来讲,关注点仍是很是少
onIntercept()
方法,判断何时须要拦截事件,须要拦截时返回true
onTouch()
方法,若是处理了事件,返回true
上面的处理思路虽然实现了需求,但可能会致使一个问题:里边的子 View 接收了一半的事件,可能都已经开始处理并作了一些事情,父 View 突然就不把后续事件给它了,会不会违背用户操做的直觉?甚至出现更奇怪的现象?
这个问题确实比较麻烦,分两类状况讨论
pressed
状态,若是你设置了对应的background
,你的 View 就会有高亮效果MOVE
事件了,这会有问题,就这个按下高亮的例子,若是你只是不传MOVE
事件了,那谁来告诉里边的子View取消高亮呢?因此你须要在中断的时候也传一个结束事件
UP
事件吗?也是不行的,由于这样就匹配了里边点击的模式了,会直接触发一个点击事件,这显然不是咱们想要的CANCEL
onTouch()
的同时,另外生成一个新的事件发给本身的子View,事件类型是CANCEL
,它将是子View收到的最后一个事件ViewPager
里有三个page,page里是ScrollView
,ViewPager
能够横向滑动,page里的ScrollView
能够竖向滑动
ViewPager
把事件给里边ScrollView
以后,它也会偷偷观察,若是你一直是竖向滑动,那没话说,ViewPager
不会触发拦截事件ViewPager
就会开始紧张,想「组织终于决定是我了吗?真的假的,那我可就不客气了」,因而在你斜滑必定距离以后,突然发现,你划不动ScrollView
了,而ViewPager
开始动ScrollView
的竖滑被取消了,ViewPager
把事件拦下来,开始横滑ScrollView
里有一些按钮,按钮有长按事件,长按再拖动就能够移动按钮
ScrollView
把事件拦下来呢?dispatch
方法返回一个特别的值给外边(以前只是true
和false
,如今要加一个)requestDisallowInterceptTouchEvent()
,子View调用它改变父View的一个状态,同时父View每次在准备拦截前都会判断这个状态(固然这个状态只对当前事件流有效)因此,连同上一次试造,总结一下
DOWN
事件时就肯定,只能在后续的事件流中进一步判断CANCEL
事件给儿子就完了另外有几个值得一提的地方:
onTouch()
后,onTouch()
只会收到后半部分的事件,这样会不会有问题呢?
在「四造」的基础上,修改获得如下代码:
interface ViewParent {
fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean)
}
open class MView {
var parent: ViewParent? = null
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
open class MViewGroup(private val child: MView) : MView(), ViewParent {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
private var isDisallowIntercept = false
init {
child.parent = this
}
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
// add cancel
val cancel = MotionEvent.obtain(ev)
cancel.action = MotionEvent.ACTION_CANCEL
handled = child.dispatch(cancel)
cancel.recycle()
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
isDisallowIntercept = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
override fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean) {
this.isDisallowIntercept = isDisallowIntercept
parent?.requestDisallowInterceptTouchEvent(isDisallowIntercept)
}
}
复制代码
此次改动主要是增长了发出CANCEL
事件和requestDisallowInterceptTouchEvent
机制
CANCEL
事件时有一个细节:没有在给 child
分发CANCEL
事件的同时继续把原事件分发给本身的onTouch
2. 这是源码中的写法,不是我故意的,多是为了让一个事件也只能有一个View处理,避免出现bugrequestDisallowInterceptTouchEvent
机制时,增长了ViewParent
接口
虽然目前整个框架的代码有点复杂,但对于使用者来讲,依然很是简单,只是在上一版框架的基础上增长了:
requestDisallowInterceptTouchEvent()
方法onTouch
方法中对事件消费而且作了一些操做,须要注意在收到CANCEL
事件时,对操做进行取消到这里,事件分发的主要逻辑已经讲清楚了,不过还差一段 Activity 中的处理,其实它作的事情相似ViewGroup,只有这几个区别:
onTouch()
因此很少讲了,直接补上Activity的麻雀:
open class MActivity(private val childGroup: MViewGroup) {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
open fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = childGroup.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
handled = childGroup.dispatch(ev)
}
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
复制代码
到这里,咱们终于造好了一个粗糙但不劣质的轮子,源码的主要逻辑与它的区别不大,具体区别大概有:TouchTarget
机制、多点触控机制、NestedScrolling 机制、处理各类 listener、结合View的状态进行处理等,相比主要逻辑,它们就没有那么重要了,你们能够自行阅读源码,以后有空也会写关于多点触控和TouchTarget
的内容 (挖坑预警)
轮子的完整代码能够在在这里查看(Java版本) 这个轮子把源码中与事件分发相关的内容剥离了出来,能看到:
但轮子不是最重要的,最重要的是整个演化的过程。
因此回头看,你会发现事件分发其实很简单,它的关键不在于「不一样的事件类型、不一样的View种类、不一样的回调方法、方法不一样的返回值」对事件分发是怎么影响的。 关键在于 「它要实现什么功能?对实现效果有什么要求?使用了什么解决方案?」,从这个角度,就能清晰并且简单地把事件分发整个流程梳理清楚。
事件分发要实现的功能是:对用户的触摸操做进行反馈,使之符合用户的直觉。
从用户的直觉出发能获得这么两个要求:
第二个要求是最难实现的,若是有多个View均可以消费触摸事件,怎么断定哪一个View更适合消费,而且把事件交给它。 咱们使用了一套简单但有效的先到先得策略,让内外的可消费事件的View拥有近乎平等的竞争消费者的资格:它们都能接收到事件,并在本身断定应该消费事件的时候去发起竞争申请,申请成功后事件就所有由它消费。
(转载请注明做者:RubiTree,地址:blog.rubitree.com )
可能有人会问,听你纸上谈兵了半天,你讲的真的跟源码同样吗,这要是不对我不是亏大了。 问的好,因此接下来我会使用一个测试事件分发的日志测试框架对这个小麻雀进行简单的测试,还会有实践部分真刀真枪地把上面讲过的东西练起来。
测试的思路是经过在每一个事件分发的钩子中打印日志来跟踪事件分发的过程。 因而就须要在不一样的 View 层级的不一样钩子中,针对不一样的触摸事件进行不一样的操做,以制造各类事件分发的场景。
为了减小重复代码简单搭建了一个测试框架(全部代码都能在此处查看),包括一个能够代理 View 中这些的操做的接口IDispatchDelegate
及其实现类,和一个DispatchConfig
统一进行不一样的场景的配置。 以后建立了使用统一配置和代理操做的 真实控件们SystemViews
和 咱们本身实现的麻雀控件们SparrowViews
。
在DispatchConfig
中配置好事件分发的策略后,直接启动SystemViews
中的DelegatedActivity
,进行触摸,使用关键字TouchDojo
过滤,就能获得事件分发的跟踪日志。 同时,运行SparrowActivityTest
中的dispatch()
测试方法,也能获得麻雀控件的事件分发跟踪日志。
先配置策略,模拟View
和ViewGroup
都不消费事件的场景:
fun getActivityDispatchDelegate(layer: String = "Activity"): IDispatchDelegate {
return DispatchDelegate(layer)
}
fun getViewGroupDispatchDelegate(layer: String = "ViewGroup"): IDispatchDelegate {
return DispatchDelegate(layer)
}
fun getViewDispatchDelegate(layer: String = "View"): IDispatchDelegate {
return DispatchDelegate(layer)
}
复制代码
能看到打印的事件分发跟踪日志:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept_BE |type:down
|layer:SViewGroup |on:Intercept_AF |result(super):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch_BE |type:down
|layer:SView |on:Touch_AF |result(super):false |type:down
|layer:SView |on:Dispatch_AF |result(super):false |type:down
|layer:SViewGroup |on:Touch_BE |type:down
|layer:SViewGroup |on:Touch_AF |result(super):false |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:down
|layer:SActivity |on:Touch_BE |type:down
|layer:SActivity |on:Touch_AF |result(super):false |type:down
|layer:SActivity |on:Dispatch_AF |result(super):false |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
...
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SActivity |on:Touch_BE |type:up
|layer:SActivity |on:Touch_AF |result(super):false |type:up
|layer:SActivity |on:Dispatch_AF |result(super):false |type:up
复制代码
BE
表明 before
,表示该方法开始处理事件的时候,用AF
表明after
,表示该方法结束处理事件的时候,而且打印处理的结果View
和ViewGroup
都不消费DOWN
事件时,后续事件将再也不传递给View
和ViewGroup
再配置策略,模拟View
和ViewGroup
都消费事件,同时ViewGroup
在第二个MOVE
事件时认为本身须要拦截事件的场景:
fun getActivityDispatchDelegate(layer: String = "Activity"): IDispatchDelegate {
return DispatchDelegate(layer)
}
fun getViewGroupDispatchDelegate(layer: String = "ViewGroup"): IDispatchDelegate {
return DispatchDelegate(
layer,
ALL_SUPER,
// 表示 onInterceptTouchEvent 方法中,DOWN 事件返回 false,第一个 MOVE 事件返回 false,第二个第三个 MOVE 事件返回 true
EventsReturnStrategy(T_FALSE, arrayOf(T_FALSE, T_TRUE, T_TRUE), T_SUPER),
ALL_TRUE
)
}
fun getViewDispatchDelegate(layer: String = "View"): IDispatchDelegate {
return DispatchDelegate(layer, ALL_SUPER, ALL_SUPER, ALL_TRUE)
}
复制代码
能看到打印的事件分发跟踪日志:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept |result(false):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch |result(true):true |type:down
|layer:SView |on:Dispatch_AF |result(super):true |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:down
|layer:SActivity |on:Dispatch_AF |result(super):true |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(false):false |type:move
|layer:SView |on:Dispatch_BE |type:move
|layer:SView |on:Touch |result(true):true |type:move
|layer:SView |on:Dispatch_AF |result(super):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(true):true |type:move
|layer:SView |on:Dispatch_BE |type:cancel
|layer:SView |on:Touch_BE |type:cancel
|layer:SView |on:Touch_AF |result(super):false |type:cancel
|layer:SView |on:Dispatch_AF |result(super):false |type:cancel
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Touch |result(true):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Touch |result(true):true |type:up
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:up
|layer:SActivity |on:Dispatch_AF |result(super):true |type:up
复制代码
ViewGroup
拦截事件先后,事件是如何分发的除了以上场景外,我也模拟了其余复杂的场景,能看到系统控件和麻雀控件打印的日志如出一辙,这就说明了麻雀控件中的事件分发逻辑,确实与系统源码是一致的。
并且从打印的日志中,能清晰地看到事件分发的轨迹,对理解事件分发过程也有很大的帮助。因此你们若是有须要,也能够直接使用这个框架像这样对触摸事件分发的各类状况进行调试。
实际上进行事件分发的实践时,会包括两方面内容:
GestureDetector
,它用起来很是方便时间关系,这部分暂时直接去看另外一篇透镜《看穿 > NestedScrolling 机制》吧,它提供了过得去的实践场景。
(以为对你有帮助的话,不妨点个赞再走呀~ 给做者一点继续写下去的动力)
4.1.事件分发经典伪代码
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
复制代码