5.View的事件分发机制/事件处理机制原理分析

news2024/11/28 6:53:55

事件MotionEvent包含了哪几个?

  1. ACTION_DOWN 手指触碰到屏幕时触发,只会执行一次
  2. ACTION_MOVE 手指在屏幕上滑动出发,会执行多次
  3. ACTION_UP 手指抬起离开屏幕出发,只会执行一次
  4. ACTION_CANCEL 事件被上层拦截时会触发
    • 父容器ViewGroup需要从子View手中抢夺分发的事件进行处理时,会用到ACTION_CANCEL
    • ViewGroup的事件分发中,通过如下代码会将事件状态置为ACTION_CANCEL:
      final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
    • 所以,在父容器ViewGroup在进行事件分发时,父容器ViewGroup需要将事件从子View中抢夺过来;
      • 处理第一次ACTION_MOVE事件中,cancelChild值为true时,会通过dispatchTransformedTouchEvent()方法中,将状态更改为ACTION_CANCEL,这个操作会执行一次子View的事件分发dispatchTouchEvent方法,并且状态是ACTION_CANCEL,也就是说子View没有消费该事件,这会将子View中的事件剥夺给到父容器ViewGroup;
      • 在父容器中进行第二次ACTION_MOVE的操作时,父容器ViewGroup进行事件处理

总结:
View是负责事件处理onTouchEvent的,而ViewGroup主要是负责事件分发的dispatchTouchEvent() .

事件的接收流程?(事件是在哪里接收的?)

  1. 通过WindowInputEventReceiver接收事件
  • ViewRootImpl.javasetView()方法中,通过如下对象WindowInputEventReceiver去接收我们的事件,代码如下:
    mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());
  1. 接收到的事件最后进入到ActivitydispatchTouchEvent()去处理,过程如下:
  • 接着我们去研究WindowInputEventReceiver中的onInputEvent方法,
  • 然后调用enqueueInputEvent方法,
  • 接着调用doProcessInputEvents方法,
  • 然后调用deliverInputEvent(q)方法,
  • 接着调用stage.deliver(q)方法,
  • 接着调用onProcess(q)方法,注意这里stage实际调用的是实现类ViewPostImeInputStageonProcess方法,
  • 接着调用processPointerEvent(q)方法,
  • 然后调用mView.dispatchPointerEvent(event),注意这里的mViewDecorView,在DecorView的终极超类View中才有重写dispatchPointerEvent(event)方法;
  • 接着调用View.java中的dispatchTouchEvent方法,这里会调用到实现类DecorView中的dispatchTouchEvent方法,
  • 这里会通过cb.dispatchTouchEvent(ev)调用到Activity中的dispatchTouchEvent()方法
    • 解释说明Activity由来:
      //这里的变量cb其实是Activity,
      //因为在(PhoneWindow)Window中setCallback(callback)方法,
      //传入的参数值是Activity的this引用
      Window.Callback cb = mWindow.getCallback();
      
  1. 接着从Activity中的dispatchTouchEvent()方法往下分析;
  2. 接着执行PhoneWindow中的superDispatchTouchEvent()方法;
  3. 接着执行DecorView中的superDispatchTouchEvent方法;
  4. 接着通过super.dispatchTouchEvent(event)执行到了ViewGroup中的dispatchTouchEvent()方法
  5. 最后将事件分发给ViewGroupdispatchTouchEvent()方法
  6. 然后分发给ViewdispatchTouchEvent()方法,最后交由ViewonTouchEvent()方法进行事件处理

总结:
ViewRootImplWindowInputEventReceiver接收事件,依次交给ActivityPhoneWindowDecorViewViewGroupdispatchTouchGroup()方法将事件分发下去,最终会由ViewonTouchEvent()方法对我们的事件进行处理.

事件分发机制流程图如下:
在这里插入图片描述

事件处理的几个方法

  • dispatchTouchEvent 分发事件
  • onInterceptTouchEvent 拦截事件
  • onTouchEvent 处理事件

View的dispatchTouchEvent()方法分析
这个方法中包含如下代码逻辑:

  //这里的li和li.mOnTouchListener是在view.setOnTouchListener函数中赋值的,所以这两个逻辑判断都成立;
  //同时(mViewFlags & ENABLED_MASK) == ENABLED也成立,所以就会调用onTouch方法,也就是调用我们页面中的OnTouchListener的回掉方法onTouch(),如果该方法返回true,这个if判断就成立,result的值为true;若不成立,result的值仍为false
  if (li != null && li.mOnTouchListener != null
          && (mViewFlags & ENABLED_MASK) == ENABLED
          && li.mOnTouchListener.onTouch(this, event)) {
      result = true;
  }
  //若result返回值为false,就会执行onTouchEvent方法,该方法中会执行onClick方法,所以如果onTouch方法返回值为true,那么就不会执行onTouchEvent方法中的onClick方法
  //短路与&&,不执行后面的逻辑判断
  if (!result && onTouchEvent(event)) {
      result = true;
  }
  • 假如在Activity中调用view.setOnTouchListener,同时回调onTouch方法,该方法返回boolean类型的值;
  • onTouch方法返回true,则上面代码中result值为true;若onTouch方法返回false,result值为false;
  • result值为false,进入第二个if逻辑判断时会调用onTouchEvent方法中的onClick方法;
  • result值为true,则不会调用onTouchEvent方法中的onClick方法;

分析onTouchEvent中的到onClick方法的经历了哪些过程

  • 通过日志,发现在onToucheEvent中的MotionEvent.ACTION_UP的时候执行了onClick方法;
  • 接着执行了mPerformClick = new PerformClick();
  • 接着执行了performClickInternal()方法;
  • 接着执行了performClick()方法;
  • 这个方法就会去执行我们的li.mOnClickListener.onClick(this)方法,也就是onClick()方法;

总结:
ViewonToucheEvent()中的MotionEvent.ACTION_UP逻辑中执行了onClick()方法,执行了onClick()方法就表示该事件被消费了!!!
执行了onClick()方法就表示该事件被消费了!!!
执行了onClick()方法就表示该事件被消费了!!!

分析:

  • onTouch和onClick的关系,执行的位置?
    • onTouchonClick是冲突关系;
    • onTouch方法返回true,就不会执行onClick方法;
    • onTouch方法返回false,执行完onTouch方法后,还会继续执行onClick方法.
  • onTouchEvent在哪里执行的?
    • onTouchEventdispathTouchEvent方法中执行,执行条件是在设置setOnTouchListener监听事件,根据onTouch方法返回值为false,dispathTouchEvent方法中短路与不成立,才会执行onTouchEvent()方法,否则只会执行onTouch事件.
  • onLongClick
    • onTouchEvent中的MotionEvent.ACTION_DOWN逻辑判断中,会执行包含有onLongClick方法的逻辑代码,如果长按时间过短,在MotionEvent.ACTION_UP中会移除长按事件,执行代码:
      removeLongPressCallback();
  • 按下view不松手,手指移到view外面,为什么不执行onClick方法?
    • 因为将手指移出View外面,也就是在执行onTouchEvent中的MotionEvent.ACTION_MOVE逻辑判断中,有一个中心点移出View外的判断;
      if (!pointInView(x, y, touchSlop)) {
          // Outside button
          // Remove any future long press/tap checks
          removeTapCallback();
          removeLongPressCallback();
          if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
              //这个方法会将执行mPrivateFlags &= ~PFLAG_PRESSED
              //这就导致在MotionEvent.ACTION_UP逻辑判断中不会走onClick的逻辑判断
              setPressed(false);
          }
          mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
      }
      
    • 中心点移出View外面,会导致在MotionEvent.ACTION_UP逻辑中,包含有onClick的逻辑判断不成立,所以就不会执行onClick方法.

ViewGroup的dispatchTouchEvent事件分发流程?

  • 第一块拦截: (判断是拦截,并生成变量intercepted;值true会拦截第二块代码,值为false不会拦截第二块代码)
    • 可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法更改事件分发中disallowIntercept变量的值,
    • 这个值disallowIntercept决定了是否执行事件拦截方法onInterceptTouchEvent()
    • 然后将事件拦截方法onInterceptTouchEvent()的返回值赋值给一个变量intercepted
  • 第二块拦截:(遍历子View,询问子View是否处理事件)
    • 判断第一块代码拦截到的变量intercepted和另外一个变量组成的逻辑判断是否成立,if (!canceled && !intercepted){...},成立则进入该if判断
    • 将子View添加到集合中,并且从集合中倒序取出子View;类似于FrameLayout层级结构,布局文件中最后的的布局文件展示在屏幕的最顶层
    • 遍历所有子View,判断手指触摸点是否在该子View身上,在该子View身上再询问子View是否消费该事件
    • 通过dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法来判断,事件是否被该子View消费掉了,该方法返回true表示子View消费了该事件,最终返回一个变量标记事件消费过了:
      alreadyDispatchedToNewTouchTarget = true;
    • 同时这个方法中会判断,是由ViewGroup还是由他的父类View来处理事件分发,代码如下:
      if (child == null) {
          //ViewGroup的super父类是View,所以这里调用View的事件分发
          //会判断事件是否在View的事件分发中进行处理
          handled = super.dispatchTouchEvent(event);
      } else {
          //这里调用ViewGroup的事件分发
          handled = child.dispatchTouchEvent(event);
      }
      
  • 第三块代码:(所有的子View都没有消费该事件,那么询问当前ViewGroup是否处理该事件)
    • 通过这个方法来询问当前ViewGroup是否处理事件,dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS),该方法返回true,表示ViewGroup消费了该事件;否则,表示事件最终都没有被消费,最后ViewGroupdispatchTouchEvent()方法返回false;
    • 注意:这里第三个参数传的null,在询问子View是否处理该事件的时候,第三个参数传的子View child.

总结:
ViewGroupdispatchTouchEvent()方法中有3块拦截代码:

  1. 第一块代码判断事件是否允许被拦截,允许被拦截才会执行onInterceptTouchEvent(ev)方法;
  2. 第二块代码判断父容器ViewGroup中的子View是否会消费该事件;前提是父容器ViewGroup不拦截,也就是onInterceptTouchEvent(ev)方法返回false;
  3. 第三块代码判断当前父容器ViewGroup是否消费该事件.

结论:
如果子View和当前父容器ViewGroup都没有消费该事件,就会把该事件依次分发给DecorViewPhoneWindowActivity来处理,如果事件还没有被处理,那么该事件就不会被处理.

View的事件分发dispatchTouchEvent方法分析

  • 根据OnTouchListeneronTouch()方法的返回值,会判断是否执行ViewonTouchEvent()方法;
  • 根据在ViewonTouchEvent()方法中会判断是否执行onClick()onLongClick()方法;
  • 如果ViewdispatchTouchEvent()方法返回false,表示View中不会对事件进行处理.

总结:

  • 父容器ViewGroup负责事件分发,最终分发给ViewGroupsuper.dispatchTouchEvent()方法,ViewGroupsuper父类是View,也就是执行View中的事件分发dispatchTouchEvent()方法;
  • 这个ViewdispatchTouchEvent()方法中会执行onTouch()方法;
    • onTouch()方法返回true,表示事件被消费掉了;
    • onTouch()方法返回false,同onTouchEvent()方法一起进行短路与&&运算,只有onTouch()方法返回false才会执行onTouchEvent()方法,onTouchEvent()方法返回true表示事件被消费掉了.
  • View的事件分发dispatchTouchEvent()方法返回true表示事件被消费掉了;否则,表示事件没有被消费.

单指操作和多指操作

  • 单指操作:
    • MotionEvent_ACTION_DOWN 只会执行一次
    • MotionEvent_ACTION_MOVE
    • MotionEvent_ACTION_MOVE
    • MotionEvent_ACTION_UP 只会执行一次
  • 多指操作:
    • MotionEvent_ACTION_DOWN 只会执行一次 第1根手指按下
    • MotionEvent_ACTION_POINTER_DOWN 这里是第2根手指按下
    • MotionEvent_ACTION_POINTER_DOWN 这里是第3根手指按下
    • MotionEvent_ACTION_MOVE
    • MotionEvent_ACTION_MOVE
    • MotionEvent_ACTION_POINTER_UP 倒数第3根手指抬起
    • MotionEvent_ACTION_POINTER_UP 倒数第2根手指抬起
    • MotionEvent_ACTION_UP 只会执行一次 最后1根手指抬起

处理View嵌套冲突的方法?

  • 内部处理法
    • 在子View中,根据条件来判断事件由子View处理,还是父容器ViewGroup处理
    • 主要是通过这个方法来处理:
      getParent().requestDisallowInterceptTouchEvent(true);
  • 外部处理法
    • 在父容器ViewGroup中,判断事件由子View处理,还是由父容器ViewGroup处理.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524519.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SCI】综合能源系统中热电联产、电制气和碳捕集系统的建模与优化(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

Java 网络编程 —— 非阻塞式编程

线程阻塞概述 在生活中,最常见的阻塞现象是公路上汽车的堵塞。汽车在公路上快速行驶,如果前方交通受阻,就只好停下来等待,等到公路顺畅,才能恢复行驶。 线程在运行中也会因为某些原因而阻塞。所有处于阻塞状态的线程…

C++数据结构:哈希 -- unordered系列容器、哈希表的结构以及如何通过闭散列的方法解决哈希冲突

目录 一. unordered系列关联式容器 1.1 unrodered_map和unordered_set 综述 1.2 常见的接口函数(以unordered_map为例) 1.3 unordered系列与map和set的效率对比 二. 哈希表的底层结构 2.1 什么是哈希 2.2 哈希函数 2.3 哈希冲突 三. 通过闭散列的…

JavaEE(系列2) -- 多线程(创建多线程)

讲述下面的内容之前,先来回顾一下一个重要的知识点 进程和线程之间的区别 1.进程包括线程。 2.进程有自己独立的内存空间和文件描述符表。同一个进程中的多个线程之间,共享同一份地址空间和文件描述符表。 3.进程是操作系统资源分配的基本单位,线程是操作…

5.14学习周报

文章目录 前言文献阅读摘要介绍方法模型框架评价指标结果结论 时间序列预测总结 前言 本周阅读文献《A Hybrid Model for Water Quality Prediction Based on an Artificial Neural Network, Wavelet Transform, and Long Short-Term Memory》,文献主要提出了基于人…

iconfont-extract: 一个将iconfont图标转化为React组件的工具

iconfont 提供了海量的图标,同时也方便了前端开发者使用这些图标,只需要添加对应的js、css或者字体文件即可。在我们的项目中使用添加js文件的方式,js文件中都包含了所有的图标,一个项目中通常只会使用其中的一部分,所…

【架构设计】DDD 到底解决了什么问题

文章目录 前言一、架构设计是为了解决系统复杂度1.1 架构设计的误区1.1.1 每个系统都要做架构设计/公司流程要求有架构设计1.1.2 架构设计是为了追求高性能、高可用、可扩展性等单一目标 1.2 架构设计的真正目的1.3 系统复杂度的六个来源及通用解法1.3.1 高性能1.3.1.1 单机复杂…

JavaWeb-一篇文章带你入门CSS(笔记+案列)

目录 CSS是什么基本语法 CSS的引入方式内部样式表行内样式表外部样式表 选择器基础选择器标签选择器类选择器id选择器通配符选择器 复合选择器后代选择器子选择器 常用元素属性字体属性文本属性背景属性圆角矩形 元素的显示模式块级元素行内元素 我们可以使用display属性来修改…

对称加密/非对称加密

古典密码学 起源于古代战争:在战争中,为了防止书信被截获后重要信息泄露,人们开始对书信进行加密。 移位式加密 如密码棒,使用布条缠绕在木棒上的方式来对书信进行加密。 加密算法:缠绕后书写 密钥: 木棒的尺寸 替…

[笔记]深入解析Windows操作系统《四》管理机制

文章目录 前言4.1注册表查看和修改注册表注册表用法注册表数据类型注册表逻辑结构HKEY_CURRENT_USERHKEY_USERS 实验:观察轮廓加载和卸载HKEY_CLASSES_ROOTHKEY_LOCAL_MACHINE 实验:离线方式或远程编辑BCDHKEY_CURRENT_CONFIGHKEY_PERFORMANCE_DATA 前言 本章讲述了…

day3_垃圾回收器

文章目录 Serial回收器ParNew回收器Parallel Scavenge回收器Serial Old回收器Parallel Old回收器CMS(Concurrent Mark Sweeping)回收器G1 主要有7种垃圾回收器,如下所示: 其中有直线关联的表示,这2种垃圾回收器可以配合使用的。 S…

大模型之PaLM2简介

1 缘起 大模型时代。 时刻关注大模型相关的研究与进展, 以及科技巨头的商业化大模型产品。 作为产品&技术普及类文章,本文将围绕PaLM2是什么、特点、如何使用展开。 想要了解更多信息的可以移步官方网站提供的参考文档,后文会给出相关链…

Oracle11g服务说明

一、服务说明 1.OracleDBConsoleorcl:非必须启动 Oracle数据库控制台服务,orcl是Oracle的实例标识,默认的实例为orcl。在运行Enterprise Manager(企业管理器OEM)的时候,需要启动这个服务。 2.OracleJobS…

一文了解异步编程

promise 什么是promise promise是异步编程的一种解决方案,从语法上来说,Promise是一个对象,从它可以获取异步操作的消息 ES6规定,Promise对象是一个构造函数,接受一个函数作为参数,这个函数会立即执行&a…

Long类型返回前端精度丢失

【1】给前端返回Long会出现精度丢失问题 在《阿里巴巴Java开发手册》中,有一条关于前后端超大整数返回的规约,具体内容如下: 【2】问题复现 后端直接用postman测试接口,返回数据没有问题。但是前端访问接口的时候,发…

系统化了解Micrometer

本文从官方文档整理出一篇系统化全面了解的文章, 后续可能会慢慢补上源码层面的解析: https://micrometer.io/docs 学习本文的目的在于深入了解中间件的监控模块的设计, 先看看主流的做法于核心思想 本文的引用来的笔者的理解于备注 需要做的是: 先理解功能存在的理由设计模式…

kibana 代码执行 (CVE-2019-7609)

漏洞原理 “原型污染”是一种针对JS语言开发的攻击方法。JS语言中,每一个对象都是有其原型(proto)的,而该原型又有其自己的原型对象,直到某个对象的原型为null。而如果JS对其中定义的对象原型的属性读写缺乏控制&…

进程程序替换+简易版shell实现

索引 进程程序替换如何进行程序替换不同程序替换函数之间的区别系统接口调用其他语言的函数替换函数execle 简易版shell实现 进程程序替换 什么是进程程序替换? 指在一个正在运行的进程中,将原来的程序替换成新的程序的过程。 eg:如果我们想…

I IntelliJ IDEA 2023.1 最新解锁方式,支持java20,让Coding飞一会儿

IntelliJ IDEA 2023.1 最新变化 在 IntelliJ IDEA 2023.1 中,我们根据用户的宝贵反馈对新 UI 做出了大量改进。 我们还实现了性能增强,从而更快导入 Maven,以及在打开项目时更早提供 IDE 功能。 新版本通过后台提交检查提供了简化的提交流程…

Android不基于第三发依赖包解析shp文件(2)

接着上篇文章继续 2)Point (点)   一个 Point 由一对双精度坐标组成,存储顺序为 X,Y。    /*** PointGeometry记录读取* */static Geometry renderPointGeometry(byte[] recordContent,GeometryFactory geometryFactory) {int shapetype2