美文网首页Android developerIT移动端
可能是讲解Android事件分发最好的文章

可能是讲解Android事件分发最好的文章

作者: 工程师milter | 来源:发表于2016-06-29 00:59 被阅读16147次

我几乎看过国内讲解Android事件分发的所有文章,但遗憾的是都没有这篇讲的好,原因有二:它阐明了具体的事件分发机制的设计意图,让人既知其然,又知其所以然;它没有贴源码,吓唬本宝宝。所以我决定将它翻译出来,造福广大Android开发者。原文请点击这里

有时,你必须要自己处理触摸事件(touch events)而不能依赖于有可用的onSomethingListener。我就遇到过这样的时候,当时我很想有一篇文章能简单地解释触摸事件是怎样在视图层次(view hierarchy)中传播的,从而可以将之作为进一步深入学习的起点。这篇博客是我的一次尝试,它看起来有点长,但这是因为我是按照触摸事件的传播过程一步一步来写的。

一些假设


我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL。一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生)。当我们说到“手势剩余部分”时指的是手势后续的MOVE事件和最后的UP或CANCEL事件。

在这里我也不考虑多点触摸手势(我们只假设用一个手指)并且忽略多个MOVE事件可以被归为一组这一实际情况。最后,我们假设文中的view都没有注册onTouchListener。

我们将要讨论的视图层次是这样的:最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup。这里我们忽略同层级view之间可能的交叉叠加。

android-touch.png

假设用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生。然后用户移动手指并最后离开屏幕,此过程中手指是否离开C的区域无关紧要,关键是手势(gesture)是从哪里开始的。

默认情况


假设上面的A,B,C都没有覆写默认的事件传播行为,那么下面就是事件传播的过程:

  • DOWN事件被传到C的onTouchEvent方法中,该方法返回false,表示“我不关心这个手势(gesture)”。
  • 因此,DOWN事件被传到B的onTouchEvent方法中,该方法同样返回false,表示B也不关心这个手势。
  • 同样,因为B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false。

由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件。

处理事件


现在,让我们假设C实际上是关心这个手势(gesture)的,原因可能是C被设置成可点击的(clickable)或者你覆写了C的onTouchEvent方法。

  • DOWN事件被传递给C的onTouchEvent方法,该方法可以做任何它想做的事情,最后返回true。
  • 因为C说它正在处理这个手势(gesture),则DOWN事件将不再被传递给B和A的onTouchEvent方法。
  • 因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。

个人理解:从这里可以看出,各个View的onTouchEvent方法对DOWN事件的处理,代表了该View对以此DOWN开始的整个手势(gesture)的处理意愿,返回true代表愿意处理该gesture,返回false代表不愿意处理该gesture。

onInterceptTouchEvent


现在我们将讨论一个新的方法:onInterceptTouchEvent,它只存在于ViewGroup中,普通的View中没有这个方法。在任何一个view的onTouchEvent被调用之前,它的父辈们(ancestors)将先获得拦截这个事件的一次机会,换句话说,它们可以窃取该事件。在刚才的“处理事件”部分中,我们遗漏了这一过程,现在,让我们把它加上:

  • DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
  • DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,因此该方法也返回false。
  • 现在,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,因为它想处理以该事件为首的手势(gesture)。
  • 现在,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也同样如此。
  • 然后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中一样。
  • “手势剩余部分”中其他事件的处理过程和上面一样,假如A和B的onInterceptTouchEvent方法继续返回false的话。

这里有两点需要注意:

  • 虽然ViewGroup A和B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件依然会传递给它们的onInterceptTouchEvent方法,这一点与onTouchEvent的行为是不一样的。
  • 假如DOWN事件传给C的onTouchEvent方法时,它返回了false,DOWN事件会继续向上传递给B和A的onTouchEvent,即使它们在onInterceptTouchEvent方法中说它们不想拦截这个DOWN事件,但没办法,没有子View愿意处理该事件。

个人理解:感谢@编程世界的孩子 的提醒,由此可见,DOWN事件的处理实际上经历了一下一上两个过程,下是指A->B的onInterceptTouchEvent,上是指C->B->A的onTouchEvent,当然,任意一步的方法中返回true,都能阻止它继续传播。

拦截事件


现在,让我们更进一步,假设B没有拦截DOWN事件,但它拦截了接下来的MOVE事件。原因可能是B是一个scrolling view。当用户仅仅在它的区域内点击(tap)时,被点击到的元素应当能处理该点击事件。但是当用户手指移动了一定的距离后,就不能再视该手势(gesture)为点击了——很明显,用户是想scroll。这就是为什么B要接管该手势(gesture)。
下面是事件被处理的顺序:

  • DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,因为它们目前还不想拦截。
  • DOWN事件传递到C的onTouchEvent方法,返回了true。
  • 在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
  • B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了一定的threshold(或者称为slop)。因此,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
  • 然后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
  • 现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。
  • 此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。
  • C再也不会收到该手势(gesture)产生的任何事件了。

下面的一些小事情可能会令你感到吃惊:

  • 如果一个ViewGroup拦截了最初的DOWN事件,该事件仍然会传递到该ViewGroup的onTouchEvent方法中。
  • 另一方面,如果ViewGroup拦截了一个半路的事件(比如,MOVE),这个事件将会被系统变成一个CANCEL事件,并传递给之前处理该手势(gesture)的子View,而且不会再传递(无论是被拦截的MOVE还是系统生成的CANCEL)给ViewGroup的onTouchEvent方法。只有再到来的事件才会传递到ViewGroup的onTouchEvent方法中。

从此开始,你可以更进一步。比如对mouthful-method (实在不知道该怎么翻译啦!)requestDisallowInterceptTouchEvent,C可以用该方法阻止B窃取事件。如果你想更加疯狂一点,你可以在你自己的ViewGroup中直接覆写dispatchTouchEvent方法,并对传递进来的事件做任何你想做的处理。但这样的话你可能会破坏一些约定,所以应当小心。


好了,文章翻译完了,不知道你是否和我一样,读完此文消解了许多的困惑,如果是的话,点赞吧!

声明:欢迎转载,只需注明本文链接即可

相关文章

网友评论

  • 墨墨_2016:楼主 , 如果点击c,且c的ontouchevent方法返回true,那么move up事件依然是从父view的dispatchTOuchevent一层层分发下来的对吗?且分发过程中会调用onInterceptTouchEvent询问父view是否拦截 ?
  • da63d864d387:很容易理解
  • starryCaptain:值得特意登录点赞
    工程师milter:@船长与柴郡猫 也为你点个赞!
  • Kuma_233:一看就明白了。。希望不要忘记了。。谢谢你:relaxed:
  • Davisxy:确实平静了许多,但是还是有个疑问:

    因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。

    说:此时该方法返回true或false都无关紧要了,为什么 呢?如果我在move中返回false,他不是应该上传该事件到父ViewGroup的onTouchEvent中的move中去吗?
    4b304a58afbb:事件流只会流向接受了down事件的控件,注意是事件流,从down...move...move...到up,是一个事件流
  • f1abf4d2779c:此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)

    困扰已久的疑问,按楼主上述,Down事件已经由C消费,B是没有消费的,那么把手势剩余部分拦截后传递给B它能消费?
    Doge_developer:@ShamsChu 你把按钮放在scroll view 然后先按在按钮上 再向下滑动 如果不能消费的话 就滑动不了了
  • developerzjy:很不错的文章,但还是不全
  • 979e2b115104:灰常好
    工程师milter: @无墨来点静 😀😏
  • 611d808d13ea:写的很细致,赞
  • 9ebb3be8d8cd:看不出来那里好了
    工程师milter:@斯嘉蒂之眼_6221 耽误你时间了
  • 紫木冰枫:没有说dispatchTouchEvent。
  • 令弧2016:还是得看源码,整个流程得看源码,这个拆解的太零散
  • Mountain_Lin:一般
    工程师milter:@Mountain_Lin 一般
  • 516021dd7081:为了评论这篇文章,特意注册了个账号。确实没吓唬到宝宝:wink: ,而且翻译的不错。之前对时间分发也算了解,但是这边文章,看起来很容易懂。毕竟重在理解,理解后代码实践起来也会很有效率!
    工程师milter: @bachcool 很高兴对你有帮助!
  • 是阿亮鸭:真心非常不错的文章,阅读了一下顿时豁然开朗了!非常感谢作者!
  • 顶级工程师闯天涯:many thx !!! 其实Android的touch处理事件就是个U型管
  • 5782bebb756b:为了给写的这么好的文章评论一个,毫不犹豫的注册了。真的是讲的最好的
    工程师milter: @hoodor 嘻嘻
  • 17bc5a1984c6:最好提供demo
  • 带心情去旅行:我看过一篇有关于事件分发的文章,感觉很不错,在这里推荐一下。http://www.jianshu.com/p/e99b5e8bd67b
    工程师milter: @带心情去旅行 确实是很好的文章
  • 9c272d3e6687:感谢楼主,我转载了
  • MeloDev:这篇文章给人一种看到一半就戛然而止的感觉,dispatch 方法没有讲解,总的体系也没有梳理完全,不过有一些细节确实以前不太清楚
    工程师milter: @MeloDev 本文适合学习很久仍有困惑的人
  • 习惯安静丶:不错,看懂了, 结合实际demo就更好了 :relaxed:
  • hackware:某些细节讲得非常不错,确实很少有文章讲到这些,但你说"可能是最好",实在不能认同
    工程师milter: @hackware 多谢鼓励,希望以后互相交流,共同提高,毕竟提高技术水平才是大家共同的追求!
    hackware: @milter 嗯,楼主入行不久,功底了得,赞
    工程师milter: @hackware 不可能所有人认同,对别人有帮助就好。
  • 03e095c54cc3:这篇文章真心简单易懂 :+1:
    工程师milter:@闵了个杰 多谢鼓励,当你不能向六岁的儿童说明一件事情的时候,说明你对这件事还没有透彻理解,一直坚信不疑的一句话,与君共勉。
  • 奥德李:收藏一下,慢慢看
  • neo已经被使用:因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。 为什么返回true或false 都无关紧要了?
    工程师milter: @neo已经被使用 因为后续的事件就是一系列的move和最后一个up,当c在down事件中返回true时,它已经告诉了系统,后续的事件都传给我。这个返回值就是用来告诉系统这件事的,现在它已经完成了。后续的事件都会交给c了,所以,在后续的事件中,返回true或者false都不重要了,因为系统只关心c对down事件的态度
  • f20112a42d88:mark,真心明白很多。
    工程师milter: @TryWang 觉得好就分享给其他朋友,让更多人受益咯😊
  • 02744cada7f7:没看完,什么都不想说了 :angry:
  • 254dd5e4a4a1:服!从某种意义上来说,这确实是Android事件分发最好的文章
  • 宇光十色_FLY:写的很好!
    工程师milter:@宇光十色_FLY 可以哈,保持原文内容完整,给俺留个名号就行
    宇光十色_FLY:@milter 可以转载吗?
    工程师milter:@宇光十色_FLY 谢谢,觉得好就分享给朋友,让大家都受益哈
  • 尚妆杨逍:如果viewgroupA中有两个子Viewgroup,点击事件会先分发给谁
  • 7133cd0cb3bf:非常不错,之前对分发是做过研究,但是这篇看完后让我懂得了更多细节。dispatch没有做什么介绍,可能会让有些人会有点迷糊。该文章原理是讲的很透彻了,可以再出一篇结合些案例就完美了。谢谢作者!
  • zhuhf:本片文章适合比较了解分发事件,但似乎没法总结出结论的。新手建议看结合源码介绍相关的文章。
    工程师milter: @hiphonezhu 是的,很适合那些看了许多事件分发的文章,却无法有一个总体把握的开发者。
  • radical0:感谢分享,翻译辛苦了。
    工程师milter: @radical0 对大家有帮助就好
  • Senble:总结的很好,谢谢楼主
  • 平头说人生:很有调理 :+1:
  • 6265b0a92ad4:觉得应该再讲下dispatch。
  • cbe7593ec1cc:个人感觉上文写的默认情况,会给人一种误解,首次受down事件影响的就是C的onTouchEvent方法
    工程师milter: @天桥下算命少年 文章后面说明了的
    工程师milter: @天桥下算命少年 首次是A,B的onInterceptTouchEvent方法
  • wordsMotivateme:收藏了
  • 秋染蒹葭:谢谢分享,非常有用,对事件拦截部分的Item6有点疑惑“现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。”这个MOVE事件还是刚才的down事件的后续事件吗?然后这个move时间就“都将传递给B的onTouchEvent方法”,这个意思是事件不再向下进行分发而直接由B的“onTouch()”放处理了是吗?
    秋染蒹葭:@milter :+1:
    工程师milter:@秋染蒹葭 关于第一点,是刚才的DOWN事件的后续事件,第二点,是不再向下向下分发,全部由B的onTouchEvent方法处理,不是onTouch
  • 大桥酱:上个分发事件的图也许会更好
    大桥酱:@milter 网上有很多,现代节奏快,不爱看文字。我上次也是看图明白的,看了你的文章加深了印象
    工程师milter:@大桥酱 画不好很容易让人产生迷惑的
    工程师milter: @大桥酱 是呀,怕画不好,没敢画
  • 少锦:不错给你个赞
    工程师milter:@少锦 😊
  • javayhu:谢谢分享 :kissing_heart: 建议在结合下源码或者实践写个第二篇,这里附上一篇可能会有用的文章,哈哈,http://hujiaweibujidao.github.io/blog/2015/12/01/Art-of-Android-Development-Reading-Notes-3/
    MISSGY_:链接挂了
    工程师milter: @宅男潇涧 你好聪明😉
  • plusend:确实适合了解了一些之后仍然有点蒙的
  • 朔野:确实讲到了很多细节,然而没有代码的分析,都是卵
    工程师milter:@雷霆之喜 那本文的目的就圆满达到了😊
    紫阚:@milter 这篇文章写的很好,起码我意识清醒的完整看完了,也大体知道整个事件流了,有了整个基础,在看其他的事件传递分析的时候,心里有底,就不会迷失在`源码`的海洋中了
    工程师milter:@朔野 文章开头已经说明,这篇文章希望成为大家深入学习事件分发的一个起点(starting point)。
  • 叶乘风:如果是对事件分发不了解的新手看了这篇估计还是一头雾水
    9b44144cfb92:确实是这样
    咸鱼佬:@milter 答的也是6
    工程师milter:@叶乘风 是的,这篇文章适合看了许多事件分发的文章仍然一头雾水的人。
  • 韦东锏:被推荐进来的,写的真的好
    工程师milter:@韦东锏 😊☺
  • 捡淑:马克
  • 爱吃鱼的外星人:只能看懂大概意思
  • 咸鱼佬:而且,DOWN事件是从底层的View开始向上传递的,当遇到愿意处理它的View时,它将停止向上的传递。
    ??


    那你前两个就不要说cba嘛
    工程师milter:@编程世界的孩子 我本文中的自底向上就是这个意思。已经修正了原文。
    咸鱼佬:@milterc到b到a是底层向顶层????
    工程师milter: @编程世界的孩子 这里可能没有说清楚,DOWN事件的处理实际上经历了一下一上两个过程,下是指A->B的onInterceptTouchEvent,上是指C->B->A的onTouchEvent,文中所说的从底层向上传递就是指的这个上。当然,任意一步的方法中返回true,都能阻止它继续传播下去。
  • ea07c6b76478:,,
    工程师milter: @lothario_6720 ?🙄
  • 82f7d163c7c2:如此不谦虚,敢不敢把可能去掉,要做就做最好,求其上者得其中
    工程师milter:@岁月不爱流年 在我看过那么多类似的文章后,这篇确实是最能解决心中疑问的。
  • leiiiooo:mark
    工程师milter:@leiiiooo 欢迎随意分享哈
    leiiiooo:@milter 会火的, :sunglasses:
    工程师milter:@leiiiooo 是要火钳刘明吗?😊

本文标题:可能是讲解Android事件分发最好的文章

本文链接:https://www.haomeiwen.com/subject/ycatjttx.html