Android 事件分发机制分析及源码详解

文章目录

  • Android 事件分发机制分析及源码详解
    • 事件的定义
    • 事件分发序列模型
      • 分发序列
      • 分发模型
    • 事件分发对象及相关方法
    • 源码分析
    • 事件分发总结

一般在实际开发中,我们很少主动去处理相关滑动处理,所以就很少关注事件分发相关机制。因为系统已经帮我们处理好了,如:ScrollView、ViewPage、ListView 等。
这里我们就以事件分发为入口,来分析一下事件分发机制与滑动冲突的解决方法。

学习 Android 开发过程中,或多或少都有听过事件分发机制,就算没听过,如果有面试经历,也大有几率被问到过吧。毕竟在现在,这个分发机制以及算是烂大街的题目了。

事件的定义

当用户触摸屏幕时,产生的一系列行为( Touch 事件)。

系统源码中通过 MotionEvent 这个类来描述这一系列相关行为。这个类中也定义了许多与事件相关的常量与变量,如事件类型、事件的相关属性等。这里就只贴出主要的四个相关类型

public final class MotionEvent extends InputEvent implements Parcelable {//按下事件:手指刚触碰屏幕public static final int ACTION_DOWN = 0;//松开事件:手指从屏幕松开public static final int ACTION_UP = 1;//移动事件:手指在屏幕上滑动public static final int ACTION_MOVE = 2;//取消事件:非人为因素取消public static final int ACTION_CANCEL = 3;
}

事件分发序列模型

分发序列

正常情况下,一次手指触摸屏幕的行为会发出一系列点击事件,可以分为如下两种情况:

  • 点击屏幕后立即松开:DOWN->UP
  • 点击屏幕滑动后再松开:DOWN->MOVE···->MOVE->UP

分发模型

我们在从宏观角度了解一下事件的分发总流程,总体来说就是:U型模型

事件先从 Activity 出发,然后传递给 DecorView(ViewGroup),经过 ViewGroup 然后在传递给相应的子 View 进行处理。如果不处理,则按照原路径逐级向上返回,直至传递到 Activity 结束。

如下流程图表述应该比较容易理解。

事件分发对象及相关方法

通过上面的分发序列和分发模型,可以看出,一个事件的分发主要涉及三个对象:Activity、ViewGroup和View。

  • Activity:控制生命周期和处理事件
  • ViewGroup:一组 View 的集合(可以包含多个子 View)
  • View:所有 UI 组件的基类

而这三个分发对象都有相应的事件处理方法

  • dispatchTouchEvent(MotionEvent event)
    用来进行事件分发
  • onInterceptTouchEvent(MotionEvent ev)
    判断是否拦截事件(值存在于 ViewGroup 中)
  • onTouchEvent(MotionEvent event)
    处理相关事件

源码分析

到这里我们对整个大致流程已经有了相关的印象了,接下来我们在从源码的角度来看看,事件是怎么传递的。代码省略了不重要的部分,并且加上了相关注释,就不过多进行解释了。

事件从 ActivitydispatchTouchEvent 方法开始向下分发进行处理。

public class Activity{public boolean dispatchTouchEvent(MotionEvent ev) {...//调用 window 实现类 PhoneWindow 的 superDispatchTouchEvent 方法if (getWindow().superDispatchTouchEvent(ev)) {return true;}//如果上面不进行处理,则调用 Activity 的 onTouchEvent 进行处理return onTouchEvent(ev);}public boolean onTouchEvent(MotionEvent event) {//如果点击区域外,则销毁当前界面,否则不进行任何处理if (mWindow.shouldCloseOnTouch(this, event)) {finish();return true;}return false;}
}

接下来在看看 PhoneWindow 怎么处理

public class PhoneWindow extends Window implements MenuBuilder.Callback {// This is the top-level view of the window, containing the window decor.//窗口的顶层视图,相当于上面模型中的 跟ViewGroupprivate DecorView mDecor;@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}
}
public class DecorView extends FrameLayout{public boolean superDispatchTouchEvent(MotionEvent event) {//最终会调用 ViewGroup 的 dispatchTouchEvent 方法return super.dispatchTouchEvent(event);}
}

先看看 ViewGroup 中的拦截逻辑

public abstract class ViewGroup extends View{@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {...boolean handled = false;...//进行安全校验和过滤,一般都为 trueif (onFilterTouchEventForSecurity(ev)) {...// 检测是否需要拦截final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}...if (!canceled && !intercepted) {...if (newTouchTarget == null && childrenCount != 0) {for (int i = childrenCount - 1; i >= 0; i--) {//倒叙遍历 子View...//将事件分发给 子Viewif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...//返回 true 表示该 view 处理事件,然后将该 view 赋值给 目标viewnewTouchTarget = addTouchTarget(child, idBitsToAssign);...}...}}...}}...return handled;}//进行一些安全性校验和过滤public boolean onFilterTouchEventForSecurity(MotionEvent event) {//noinspection RedundantIfStatementif ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {// Window is obscured, drop this touch.return false;}return true;}//是否拦截事件,在 ViewGroup 中一般返回的 falsepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false;}
}

在看看 ViewGroup 的分发逻辑

public abstract class ViewGroup extends View{private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;...if (child == null) {//调用 View 的 dispatchTouchEvent 方法handled = super.dispatchTouchEvent(event);} else {//调用 子View 的 dispatchTouchEvent 方法handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);...return handled}
}

上面事件已经从 Activity 分发到了 ViewGroup,再从 ViewGroupdispatchTransformedTouchEvent 方法调用了子 ViewdispatchTouchEvent 方法,接下来在看看 View 中对于事件是怎么处理的。

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;//当前View是否可见(未被其他窗口遮盖住,且未隐藏)if (onFilterTouchEventForSecurity(event)) {...ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {//如果设置了 OnTouchListener//则先响应 OnTouchListener.onTouch 方法result = true;}// 当 onTouch 返回 false,在执行 onTouchEvent 处理相关事件,如果处理,则返回 trueif (!result && onTouchEvent(event)) {result = true;}...}...return result;}public boolean onTouchEvent(MotionEvent event) {...final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;...//如果 View 可点击 或者 可长按,则最终一定 return trueif (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {//抬起,判断是否处理点击事件case MotionEvent.ACTION_UP:break;//按下,处理长按事件case MotionEvent.ACTION_DOWN:break;//移动,检测触摸是否划出了控件,移除响应事件case MotionEvent.ACTION_MOVE:break;}return true;}...return false;}
}

事件分发总结

  • 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外
  • ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false, View没有onlnterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
  • View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的clickable和longClickable同时为false), View的longClickable默认都为false, clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false.
  • View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true
  • onClick会响应的前提是当前View是可点击的,并且收到了ACTION-DOWN和ACTION-_UP的事件,并且受长按事件影响,当长按事
    件返回true时, onClick不会响应。
  • onLongClick在ACTION-DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener
  • 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束正常情况下,一个事件序列只能被一个view拦截并且消耗。
  • 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onlnterceptTouchEvent不会再调用。
  • 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用)。

Android 事件分发机制分析及源码详解相关推荐

  1. 【 卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10)】

    卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10) 本章主要进行卷积神经网络的相关数学原理和pytorch的对应模块进行推导分析 代码也是通过demo实 ...

  2. 【多输入模型 Multiple-Dimension 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人 (6/10)】

    多输入模型 Multiple-Dimension 数学原理分析以及源码源码详解 深度学习 Pytorch笔记 B站刘二大人(6/10) 数学推导 在之前实现的模型普遍都是单输入单输出模型,显然,在现实 ...

  3. android事件传递机制以及onInterceptTouchEvent()和onTouchEvent()详解二之小秘与领导的故事...

    总结的不是很好,自己也有点看不懂,正好现在用到了,研究了一个,再次总结,方便大家查看 总则: 1.onInterceptTouchEvent中有个Intercept,这是什么意思呢?她叫拦截,你大概知 ...

  4. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  5. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  6. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  7. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    <div id="container">         <div id="header">     <div class=&qu ...

  8. 浅谈Android事件分发机制

    在Android实际开发过程中经常会遇到View之间的滑动冲突,如ScrollView与Listview.RecyclerView之间的嵌套使用.在很好的解决此类问题之前,我们应深入的了解Androi ...

  9. Android 事件分发机制

    Android 事件分发机制  demo验证:  https://blog.csdn.net/hty1053240123/article/details/77866302 目录 1.基础认知 2.事件 ...

最新文章

  1. 2018年中美自动驾驶进展分析报告
  2. C++实现学生成绩管理系统
  3. redis 数据库主从不一致问题解决方案
  4. TestNG测试框架之测试用例的执行顺序分析
  5. java 接口 泛型示例,java泛型接口实现示例
  6. STM32的SPI驱动代码
  7. CSS3--幽灵按钮特效(实例)
  8. HDU 3947 River Problem
  9. Asp.Net第一章入门之后台处理程序
  10. 动态调用类 java_Java动态调用类中方法
  11. java 简单图片浏览器_Java实现简单的图片浏览器
  12. 更新mac系统中homebrew的源,更改为清华大学的源
  13. kprobe原理解析(一)
  14. foreach()与list()的综合应用,用list给嵌套的数组解包
  15. linux设备符,linux 字符设备(一)
  16. Matlab遗传算法
  17. edem颗粒替换_EDEM常见问题
  18. 嵌入式开发实践系列文章 - 目录
  19. 爬虫2021广东省普通专升本各院校专业招生计划汇总表
  20. A type incompatibility occurred while executing org.springframework.boot:spring-boot-maven-plugin:2.

热门文章

  1. FUTEX_SWAP补丁分析-SwitchTo 如何大幅度提升切换性能?
  2. mysql中的临时表怎么用的?
  3. 第六十六章 Caché 函数大全 $TRANSLATE 函数
  4. 安卓的第三方sdk是html,第三方SDK目录
  5. 2020年中国废旧纺织品回收行业市场现状分析,高值化再生技术持续推进「图」
  6. 互联网IT 校招与内推:软实力的技巧
  7. 使用Python获取股市融资融券数据并绘制曲线
  8. WIN7安装官方漏洞补丁,错误代码0x80240037的解决方法
  9. 大类资产配置(一)均值方差模型MOV
  10. 我辞去高薪程序员工作,转行干淘宝,每天起床睁开眼,先赔几千!转行,你怕么?...