面试:讲讲 Android 的事件分发机制

写在前面

转眼间 面试系列 已经到了第九期了,因为文章将会持续更新,致使标题难看性,因此之后的标题将更正为本文相似的格式。面试

好了,话很少说,仍是直入主题吧。微信

面试场景

讲讲 Android 的事件分发机制?布局

基本会听从 Activity => ViewGroup => View 的顺序进行事件分发,而后经过调用 onTouchEvent() 方法进行事件的处理。咱们在项目中通常会对 MotionEvent.ACTION_DOWNMotionEvent.ACTION_UPMotionEvent.ACTION_MOVEMotionEvent.ACTION_CANCEL 分状况进行操做。学习

有去查看源码中的事件拦截方法吗?或者说在进行事件分发的时候如何让正常的分发方式进行拦截?this

我知道有个拦截事件的方法叫...叫,onInterceptEvent()?应该是,不过因为平时项目较多,确实没时间去关注太多源码。spa

厄,那你以为在一个列表中,同时对父 View 和子 View 设置点击方法,优先响应哪一个?为何会这样?3d

确定是优先响应子 View 的,至于为何这样,平时知道这个结论,因此没去太深刻研究,但我相信我简单看一下源码是确定知道的。code

先发表点扯淡

咱们可能常常会遇到上面的这种状况,面试官但愿了解咱们知识的深刻状况,或者说是平时学习欲望到底怎样。可很不幸的是,我搞 模拟面试 以来,80% 的小伙伴都属于开发能力不错,可对相似事件分发这样的基础问题一律不知。究其缘由,除去忙之外,大多数小伙伴仍是以为平时开发也用不上什么,即便用到了,直接 Google 一下便能获得正确答案。orm

这大概就是不少人不会自定义 View 的缘由吧,大多数效果在 GitHub 上都是现成的了,即便不太同样,也能够简单改改完事。cdn

可很遗憾的是,我模拟面试那额外的 20% 的人,总拿到了令大多数人羡慕嫉妒恨的 offer,这不是没有缘由的。可能别人就平时的开发中保持了更多的一点求知欲,就学到了不少相当重要的细节知识。

正文

仍是不能偏题,其实这样的一个面试问题,确实是一个较为广泛的问题,我相信同类型的文章,网上一搜也是比比皆是,并且简单看一下关注度就能知道有多少人倒在了这种源码类型的面试上。

通常状况下,事件列都是从用户按下(ACTION_DOWN)的那一刻产生的,不得不提到,三个很是重要的与事件相关的方法。

  • dispatchTouchEvent()
  • onTouchEvent()
  • onInterceptTouchEvent()

Activity 的事件分发机制

从英文单词中已经很明显的知道,dispatchTouchEvent() 是负责事件分发的。当点击事件产生后,事件首先会传递给当前的 Activity,这会调用 Activity 的 dispatchTouchEvent() 方法,咱们来看看源码中是怎么处理的。

注意截图中,我增长了一些注释,便于咱们更加方便的理解,因为咱们通常产生点击事件都是 MotionEvent.ACTION_DOWN,因此通常都会调用到 onUserInteraction() 这个方法。咱们不妨来看看都作了什么。

很遗憾,这个方法实现是空的,不过咱们能够从注释和其余途径能够了解到,该方法主要的做用是实现屏保功能,而且当此 Activity 在栈顶的时候,触屏点击 Home、Back、Recent 键等都会触发这个方法。

再来看看第二个 if 语句,getWindow().superDispatchTouchEvent()getWindow() 明显是获取 Window,因为 Window 是一个抽象类,因此咱们能拿到其子类 PhoneWindow,咱们直接看看 PhoneWindows.superDispatchTouchEvent() 到底作了什么操做。

直接调用了 DecorViewsuperDispatchTrackballEvent() 方法。DecorView 继承于 FrameLayout,做为顶层 View,是全部界面的父类。而 FrameLayout 做为 ViewGroup 的子类,因此直接调用了 ViewGroupdispatchTouchEvent()

ViewGroup 的事件分发机制

咱们经过查看 ViewGroupdispatchTouchEvent() 能够发现。

注意其中红框里面的代码,看注释也能知道,定义了一个 boolean 值变量 intercept 来表示是否要拦截事件。

其中采用到了 onInterceptTouchEvent(ev)intercept 进行赋值。大多数状况下,onInterceptTouchEvent() 返回值为 false,但咱们彻底能够经过重写 onInterceptTouchEvent(ev) 来改变它的返回值,不妨继续往下看,咱们后面对这个 intercept 作了什么处理。

暂时忽略 判断的 canceled,该值一样大多数时候都返回 false,因此当咱们没有重写 onInterceptTouchEvent() 并使它的返回值为 true 时,通常状况下都是能够进入到该方法的。

继续阅读源码能够发现,里面作了一个 For 循环,经过倒序遍历 ViewGroup 下面的全部子 View,而后一个一个判断点击位置是不是该子 View 的布局区域,固然还有一些其余的,因为篇幅缘由,这里就不细讲了。

View 的事件分发机制

ViewGroup 说到底仍是一个 View,因此咱们不得不继续看看 View 的 dispatchTouchEvent()

截图中的代码是有删减的,咱们重点看看没有删减的代码。

红框中的三个条件,第一个我就不用说了。

  • (mViewFlags & ENABLED_MASK) == ENABLED 该条件是判断当前点击的控件是否为 enable,但因为基本 View 都是 enable 的,因此这个条件基本都返回 true。

  • mOnTouchListener.onTouch(this, event) 即咱们调用 setOnTouchListener() 时必须覆盖的方法 onTouch() 的返回值。

从上述的分析,终于知道「onTouch() 方法优先级高于 onTouchEvent(event) 方法」是怎么来的了吧。

再来看看 onTouchEvent()

从上面的代码能够明显地看到,只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一个为 true,那么 onTouchEvent() 就会返回 true 消耗这个事件。CLICKABLE 和 LONG_CLICKABLE 表明 View 能够被点击和长按点击,咱们一般都会采用 setOnClickListener()setOnLongClickListener() 作设置。接着在 ACTION_UP 事件中会调用 performClick() 方法,咱们看看都作了什么。

从截图中能够看到,若是 mOnClickListener 不为空,那么它的 onClick() 方法就会调用。

总结

原本写到这就结束了,但回顾一遍仍是打算给你们稍微总结一下。

须要总结的小点: 一、Android 事件分发老是遵循 Activity => ViewGroup => View 的传递顺序; 二、onTouch() 执行总优先于 onClick()

本来想用文字总结的,结果发现简书上还有这样一篇神文:Android事件分发机制详解:史上最全面、最易懂,因此直接引用一下其中的图片。

  • Activity 的事件分发示意图

  • ViewGroup 事件分发示意图

  • View 的事件分发示意图

  • 事件分发工做流程总结

我是南尘,只作比心的公众号,欢迎关注我。

作不完的开源,写不完的矫情。欢迎扫描下方二维码或者公众号搜索「nanchen」关注个人微信公众号,目前多运营 Android ,尽本身所能为你提高。若是你喜欢,为我点赞分享吧~
nanchen
相关文章
相关标签/搜索