编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1App启动流程梳理
  • 1.2ActivityThread分析
  • 1.3Context设计思想
  • 2.1Activity基础介绍
  • 2.2Activity启动流程
  • 2.3Activity布局创建
  • 2.4Activity布局绘制
  • 2.5Service基础介绍
  • 2.6Service启动流程
  • 2.7Receiver广播基础
  • 2.8Receiver深入原理
  • 2.9ContentProvider分析
  • 2.10Fragment实践
  • 2.11Intent深入思考
  • 3.1Paint和Canvas
  • 3.2View的绘制基础
  • 3.3onMeasure流程设计
  • 3.4onLayout流程设计
  • 3.5onDraw流程设计
  • 3.6View工作原理
  • 3.7View刷新设计流程
  • 3.8自定义View控件
  • 3.9自定义ViewGroup控件
  • 4.1Handler基础使用
  • 4.2消息机制流程分析
  • 4.3Handler深度解析
  • 4.4Message深度理解
  • 4.5MessageQueue解析
  • 4.6Looper深度解析
  • 4.7理解Handler同步屏障
  • 4.8ThreadLocal分析
  • 4.9ThreadLocal场景
  • 5.1View事件设计思考
  • 5.2View滑动冲突处理
  • 5.3View事件源码分析
  • 5.4View事件总结案例
  • 6.1DecorView设计思想
  • 6.2视图的载体View
  • 6.3视图管理者Window
  • 6.4窗口管理服务WMS
  • 6.5布局解析者Inflater
  • 7.1AsyncTask深入介绍
  • 7.2HandlerThread设计
  • 7.3IntentService设计
  • 8.1IPC通信方式介绍
  • 8.2AIDL进程间通信
  • 8.7Binder通信机制设计
  • /zh/android/basic/9.1注解设计思想和原理.html
  • 9.2APT技术设计详解
  • 9.3APT多种案例实践

5.1View事件设计思考

目录介绍

  • 01.事件分发概念
    • 1.1 事件分发的对象是谁
    • 1.2 事件分发的本质
    • 1.3 事件在哪些对象间进行传递
    • 1.4 事件分发过程涉及方法
    • 1.5 Android触摸事件流程总结
  • 02.事件分发机制方法
    • 2.1 dispatchTouchEvent()
    • 2.2 onTouchEvent()
    • 2.3 onInterceptTouchEvent()
    • 2.4 三个方法执行顺序
    • 2.5 三者之间关系
  • 03.事件分发背景描述
    • 3.1 先看一个案例
    • 3.2 该案例事件传递情况
  • 04.拖动过程业务举例
    • 4.1 先看一个案例

01.事件分发概念

1.1 事件分发的对象是谁

  • 事件分发的对象是事件。
    • 注意,事件分发是向下传递的,也就是父到子的顺序。
  • 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
    • Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
  • 主要发生的Touch事件有如下四种:
    • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
    • MotionEvent.ACTION_MOVE:滑动View
    • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
    • MotionEvent.ACTION_UP:抬起View(与DOWN对应)
  • a.1 ACTION_DOWN
    • 用户手指的按下操作,一次按下的操作标志者一次触摸事件的开始。
  • a.2 ACTION_UP
    • 用户手指离开屏幕的操作,一次抬起操作标志者一次触摸事件的结束
  • a.3 ACTION_MOVE
    • 用户手指按压屏幕后,在松开之前,如果距离超过一定得阈值,那么会被判定为ACTION_MOVE。一般情况下,手指的轻微移动都会触发一系列的移动事件。
  • a.4 注意
    • 1.在一次屏幕触摸事件中,ACTION_DOWN和ACTION_UP者两个事件是必须的,而ACTION_MOVE视情况而定,如果用户仅是点击了一下屏幕,那么可能只会监测到按下和抬起的动作。
    • 2.通过MotionEvent可以获得事件发生的x和y坐标,getX和getY返回的是相当于当前View左上角的X和Y坐标,getRawX和getRawY返回的是相当于手机屏幕左上角的X和Y坐标
  • a.5 其他
    Touch触摸事件
    ♦ 在Android中Touch触摸事件主要包括:
    点击(onClick)
    长按(onLongClick)
    拖拽(onDrag)
    滑动(onScroll)
    ♦ 在Android中Touch操作状态主要包括:
    按下(ACTION_DOWN)
    移动(ACTION_MOVE)
    抬起(ACTION_UP)
    取消手势(ACTION_CANCEL)
    划出屏幕(ACTION_OUTSIDE)
  • 事件列:
    • 从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件。即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理
    • 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图[01.1图]:
    • image
      image

1.2 事件分发的本质

  • 将点击事件(MotionEvent)向某个View进行传递并最终得到处理
    • 即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
    • Android事件分发机制的本质是要解决,点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。

1.3 事件在哪些对象间进行传递

  • Activity、ViewGroup、View
    • 一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
    • Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
  • View是所有UI组件的基类
    • 一般Button、ImageView、TextView等控件都是继承父类View
  • ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),
    • 其本身也是从View派生的,即ViewGroup是View的子类
    • 是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
    • 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。

1.4 事件分发过程涉及方法

  • 事件分发过程由这几个方法协作完成
    • dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()[01.2图]

1.5 Android触摸事件流程总结

  • Android触摸事件流程总结
    • 1.一个事件序列从手指触摸屏幕开始,到触摸结束。同一事件序列是以ACTION_DOWN开始,中间有数量不定的ACTION_MOVE事件,最终以ACTION_UP结束
    • 2.事件传递顺序是:Activity——>Window——>View;最后顶级View接收到事件后,就会按照事件分发机制去分发事件
    • 3.事件传递过程是由外向内的,即事件总是有父元素分发给子元素

02.事件分发机制方法

  • 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成,如下图[01.3图]:

2.1 dispatchTouchEvent()

  • 如下所示
    属性介绍
    使用对象Activity、ViewGroup、View
    作用分发点击事件
    调用时刻当点击事件能够传递给当前View时,该方法就会被调用
    返回结果是否消费当前事件,详细情况如下:
  • 1. 默认情况:根据当前对象的不同而返回方法不同
    对象返回方法备注
    Activitysuper.dispatchTouchEvent()即调用父类ViewGroup的dispatchTouchEvent()
    ViewGrouponIntercepTouchEvent()即调用自身的onIntercepTouchEvent()
    ViewonTouchEvent()即调用自身的onTouchEvent()
  • 流程解析
    • 返回true
      • 消费事件
      • 事件不会往下传递
      • 后续事件(Move、Up)会继续分发到该View
    • 返回false
      • 不消费事件
      • 事件往下传递
      • 将事件回传给父控件的onTouchEvent()处理
        • Activity例外:返回false=消费事件
      • 后续事件(Move、Up)会继续分发到该View(与onTouchEvent()区别)

2.2 onTouchEvent()

  • 如下所示
    属性介绍
    使用对象Activity、ViewGroup、View
    作用处理点击事件
    调用时刻在dispatchTouchEvent()内部调用
    返回结果是否消费(处理)当前事件,详细情况如下:
  • 流程解析
    • 返回true
      • 自己处理(消费)该事情
      • 事件停止传递
      • 该事件序列的后续事件(Move、Up)让其处理;
    • 返回false(同默认实现:调用父类onTouchEvent())
      • 不处理(消费)该事件
      • 事件往上传递给父控件的onTouchEvent()处理
      • 当前View不再接受此事件列的其他事件(Move、Up);
  • 注意一点
    • 在这里要强调View的OnTouchListener。如果View设置了该监听,那么OnTouch()将会回调。
    • 如果返回为true那么该View的OnTouchEvent将不会在执行 这是因为设置的OnTouchListener执行时的优先级要比onTouchEvent高。
    • 优先级:OnTouchListener > onTouchEvent > onClickListener

2.3 onInterceptTouchEvent()

  • 如下所示
    属性介绍
    使用对象ViewGroup(注:Activity、View都没该方法)
    作用拦截事件,即自己处理该事件
    调用时刻在ViewGroup的dispatchTouchEvent()内部调用
    返回结果是否拦截当前事件,详细情况如下:
  • 流程解析
    • true-当前ViewGroup(因为View中没有该方法,而没有child的VIew也不需要有拦截机制)希望该事件不再传递给其child,而是希望自己处理。
    • false-当前ViewGroup不准备拦截该事件,事件正常向下分发给其child。

2.4 三个方法执行顺序

  • 代码如下所示
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        LogUtils.e("yc----------事件拦截----------");
        return super.onInterceptTouchEvent(e);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        LogUtils.e("yc----------事件分发----------");
        return super.dispatchTouchEvent(ev);
    }
    
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        LogUtils.e("yc----------事件触摸----------");
        return super.onTouchEvent(e);
    }
  • 执行结果如下
    yc----------事件分发----------
    yc----------事件拦截----------
    yc----------事件触摸----------

2.5 三者之间关系

  • 下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则
    // 点击事件产生后,会直接调用dispatchTouchEvent分发方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //代表是否消耗事件
        boolean consume = false;
    
        if (onInterceptTouchEvent(ev)) {
            //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
            //则该点击事件则会交给当前View进行处理
            //即调用onTouchEvent ()方法去处理点击事件
            consume = onTouchEvent (ev) ;
        } else {
            //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
            //则该点击事件则会继续传递给它的子元素
            //子元素的dispatchTouchEvent()就会被调用,重复上述过程
            //直到点击事件被最终处理为止
            consume = child.dispatchTouchEvent (ev) ;
        }
        return consume;
    }

03.事件分发背景描述

3.1 先看一个案例

  • 讨论的布局层次如下[01.4图]:
    • 最外层:Activiy A,包含两个子View:ViewGroup B、View C
    • 中间层:ViewGroup B,包含一个子View:View C
    • 最内层:View C
  • 触摸情况
    • 假设用户首先触摸到屏幕上View C上的某个点(如图中黄色区域),那么Action_DOWN事件就在该点产生,然后用户移动手指并最后离开屏幕。

3.2 该案例事件传递情况

  • 一般的事件传递场景有:
    • 默认情况
    • 处理事件
    • 拦截DOWN事件
    • 拦截后续事件(MOVE、UP)
3.2.1 默认情况
  • 即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
  • 那么调用的是这3个方法的默认实现:调用父类的方法
  • 事件传递情况:
    • 从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
    • 再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()
  • 注:虽然ViewGroup B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()
  • 注意:这一点与onTouchEvent的行为是不一样的。
3.2.2 处理事件
  • 假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。
    • 最常见的:设置Button按钮来响应点击事件
  • 事件传递情况:
    • DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件
    • 因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
    • 该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()
3.2.3 拦截DOWN事件
  • 假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。
  • 事件传递情况:
    • DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
    • 调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
    • 该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
  • 该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
3.2.4 拦截DOWN的后续事件
  • 假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。
    • DOWN事件传递到C的onTouchEvent方法,返回了true。
    • 在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
    • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
      1. 后续事件将直接传递给B的onTouchEvent()处理
      2. 后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
    • C再也不会收到该事件列产生的后续事件。
  • 特别注意:
    • 如果ViewGroup A 拦截了一个半路的事件(如MOVE),这个事件将会被系统变成一个CANCEL事件并传递给之前处理该事件的子View;
    • 该事件不会再传递给ViewGroup A的onTouchEvent()
    • 只有再到来的事件才会传递到ViewGroup A的onTouchEvent()

04.拖动过程业务举例

4.1 先看一个案例

贡献者: yangchong211
上一篇
4.9ThreadLocal场景
下一篇
5.2View滑动冲突处理