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

简介: 写在前面转眼间 面试系列 已经到了第九期了,由于文章将会持续更新,导致标题难看性,所以以后的标题将更正为本文类似的格式。好了,话不多说,还是直入主题吧。
+关注继续查看

写在前面

转眼间 面试系列 已经到了第九期了,由于文章将会持续更新,导致标题难看性,所以以后的标题将更正为本文类似的格式。

好了,话不多说,还是直入主题吧。

面试场景

讲讲 Android 的事件分发机制?

基本会遵从 Activity => ViewGroup => View 的顺序进行事件分发,然后通过调用 onTouchEvent() 方法进行事件的处理。我们在项目中一般会对 MotionEvent.ACTION_DOWNMotionEvent.ACTION_UPMotionEvent.ACTION_MOVEMotionEvent.ACTION_CANCEL 分情况进行操作。

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

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

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

肯定是优先响应子 View 的,至于为什么这样,平时知道这个结论,所以没去太深入研究,但我相信我简单看一下源码是肯定知道的。

先发表点扯淡

我们可能经常会遇到上面的这种情况,面试官希望了解我们知识的深入情况,或者说是平时学习欲望到底怎样。可很不幸的是,我搞 模拟面试 以来,80% 的小伙伴都属于开发能力不错,可对类似事件分发这样的基础问题一概不知。究其原因,除去忙以外,大多数小伙伴还是觉得平时开发也用不上什么,即使用到了,直接 Google 一下便能得到正确答案。

这大概就是很多人不会自定义 View 的原因吧,大多数效果在 GitHub 上都是现成的了,即使不太一样,也可以简单改改完事。

可很遗憾的是,我模拟面试那额外的 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() 方法就会调用。

总结

本来写到这就结束了,但回顾一遍还是打算给大家稍微总结一下。

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

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

  • Activity 的事件分发示意图

  • ViewGroup 事件分发示意图

  • View 的事件分发示意图

  • 事件分发工作流程总结

目录
相关文章
|
4月前
|
Android开发
Android 获取include标签中的控件属性并设置事件
Android 获取include标签中的控件属性并设置事件
73 0
|
7月前
|
设计模式 缓存 前端开发
Android 架构之 MVI 究极体 | 状态和事件分道扬镳,粘性不再是问题
Android 架构之 MVI 究极体 | 状态和事件分道扬镳,粘性不再是问题
400 0
|
10月前
|
Java Linux API
“framework必会”系列:Android Input系统(一)事件读取机制
曾经在开发的很长一段时间内,笔者对点击事件的认知只存在于自定义View中的`onTouchEvent`等方法的处理。 后来慢慢的接触到`Android的事件分发机制`,但也只是在**Activity->ViewGroup->View**层面的分发逻辑
|
10月前
|
Android开发
android 模拟发送多点触摸事件
android 模拟发送多点触摸事件
208 0
|
API Android开发
关于Android 日历事件的实现
经常购买火车票,机票的同学就知道,当我们买下一张票的时候,票的行程日期会被写入系统日历中,当火车开动或者飞机启航的前30分钟,手机会有提醒信息,这条信息是由系统日历发出的,提醒用户,别错过时间啦。 像这种系统日历提醒功能,实现起来并不难,毕竟Android 系统已经提供API给我们调用了,不需要重新造轮子,下面我们来实现这个功能。
218 0
|
开发工具 Android开发
RxBus 一个简易、非反射的Android事件通知库
RxBus 一个简易、非反射的Android事件通知库
502 0
RxBus 一个简易、非反射的Android事件通知库
|
设计模式 安全 Java
Android事件通知工具:RxBus在Eclipse和AS中的实践
Android事件通知工具:RxBus在Eclipse和AS中的实践
185 0
Android事件通知工具:RxBus在Eclipse和AS中的实践
|
Android开发
Android Touch事件分发(源码分析)
Android一文让你轻松搞定Touch事件分发 源码分析 Activity事件分发机制 Activity.dispatchTouchEvent()源码 Activity.onTouchEvent()源码 Activity源码总结 ViewGroup事件分发机制 ViewGroup.dispatchTouchEvent()源码 ViewGroup.onInterceptTouchEvent()源码 ViewGroup.onTouchEvent()源码 ViweGroup源码总结 View的事件分发机制 View.dispatchTouchEvent()源码
129 0
Android Touch事件分发(源码分析)
|
Android开发
Android一文让你轻松搞定Touch事件分发(下)
实例 创建实例 创建MyViewGroup继承ViewGroup 创建MyView继承View 创建TouchActivity继承Activity 创建布局文件 MLog.logEvent() 点击页面,看效果 点击Activity(白色区域) 点击ViewGroup(黄色区域) 点击View(蓝色区域) 结果分析 事件分发和处理 Activity处理和分发 Activity处理 运行结果 Activity分发 ViewGroup拦截处理和分发 ViewGroup拦截处理 运行结果 结果分析 运行结果 ViewGroup分发 View处理和分发 View处理 运行结果 结果分析 View分发
89 0
Android一文让你轻松搞定Touch事件分发(下)
|
调度 Android开发
Android一文让你轻松搞定Touch事件分发(上)
前言 名词了解 什么是事件 事件流 什么是事件分发 思路梳理 ViewGroup View 涉及事件分发的方法 方法的简单用途解析 拥有上述方法的类 事件分发流程
288 0
Android一文让你轻松搞定Touch事件分发(上)
热门文章
最新文章
相关产品
云迁移中心
推荐文章
更多