Android事件分发机制

news2025/1/13 10:03:06

文章目录

      • Android View事件分发机制:
        • 事件分发中的核心方法
        • onTouchListener和onClickListener的优先级
        • 事件分发
          • DOWN,MOVE,UP 事件分发
          • CANCEL
        • 代码实践
        • requestdisallowIntereptTouchEvent作用

Android View事件分发机制:

事件分发中的核心方法

Android中事件分发,实际上分发的是MotionEvent,事件分发的过程中,涉及到下面三个核心的方法:

  • dispatchTouchEvent:用来进行事件的分发,只要事件传递给当前View,那么这个方法一定会被调用,该方法的返回值受到onTouchEvent和子View的dispatchTouchEvent的影响。
  • onInterceptTouchEvent:用来询问是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,这个方法不会被再次调用。(onInterceptTouchEvent只存在于ViewGroup中,Activity和普通的View中都没有这个方法)
  • onTouchEvent:用来处理MotionEvent
    上面的方法,如果返回的是true,就表示消耗当前的事件。

onTouchListener和onClickListener的优先级

onTouchListener 优先级高于 onClickListener
onTouchListener优先级高于onClickListener,onTouchListener返回false,后续的click事件才会被处理,onTouchListener返回true表示消耗了事件,不会再传递。

事件分发

事件传递的时候是由Activity->window->view,如果view不处理的话,最后事件会回到activity,在事件的流程中:View不会分发事件,View只会处理事件,ViewGroup会先分发事件,如果子View没有处理事件,尝试自己处理事件,如果自己没有处理,最后交给Activity。

DOWN,MOVE,UP 事件分发

android.view.ViewGroup#dispatchTouchEvent

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
				// 一次事件开始,ViewGroup会先清除之前的状态,例如清楚:FLAG_DISALLOW_INTERCEPT的标记
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            // 为down事件或者mFirstTouchTarget不为空表示找到了消耗touch事件的view
            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;
            }
            ...
			// 会便利当前viewgroup的所有child,寻找是否需要消耗事件
			// Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
							// canReceivePointerEvents判断view是否可见&view没有再播放动画,
							// isTransformedTouchPointInView判断点击区域是否在view的范围内
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

ViewGroup的TouchEvent中,都会先去判断View是否有子view,有子view的话递归调用子View的dispatchTouchEvent方法,否则直接调用自己的dispatchTouchEvent方法:

  			if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }

Down事件分发后,一定会找到了消耗事件的view(或者事件直接被Activity消耗),如果ViewGroup没有消耗事件,事件就不会再继续往当前ViewGroup分发,UP和MOVE事件都会发送到DOWN事件的消耗这上,mFirstTouchTarget不为null,直接找到target进行分发。

           // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

View的dispatchTouchEvent
android.view.View#dispatchTouchEvent

    if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

View的dispatchTouchEvent中,会先判断view有没有设置TouchListener,如果设置了TouchListener再去判断onTouch方法的返回值,如果onTouch方法没有消耗事件,会再调用onTouchEvent方法。如果View 的onTouchEvent方法返回了false,就表示事件没有被消耗,那么最终会调用到ViewGroup的onTouchEvent。

         // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
		// 上面的代码会调用到dispatchTransformedTouchEvent中的下面的地方,进行事件分发:
		if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);

mFirstTouchTarget为null表示子view没有消耗事件,这里会再调用dispatchTransformedTouchEvent去派发。
ViewGroup事件派发结束后,就会回到Activity中,参考下面的代码:


    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        // 执行activity的onTouchEvent
        return onTouchEvent(ev);
    }

如果ViewGroup的onTouchEvent返回false,那么就会调用Activity的onTouchEvent。

CANCEL

Cancel事件触发场景案例:当ScrollView中添加自定义View时,ScrollView在DOWN事件不会进行拦截,当手指滑动到一定的距离后,onInterceptTouchEvent方法返回true,并触发ScrollView的滚动效果,当ScrollView进行滚动的时候,内部的子View会收到一个cancel事件,并丢失焦点。

代码实践

代码地址:https://gitee.com/lxd15130140362/lxd-android-start/tree/master/app/src/main/java/com/example/androidstart/view
界面布局样式:

  1. 都不处理事件,点击自定义的Textview日志顺序如下:
2023-02-19 20:50:19.786 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart D/CustomizeLayout: onInterceptTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/CustomizeTextView: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/CustomizeTextView: onTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.829 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:50:19.829 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:50:19.927 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:50:19.928 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:50:20.017 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:50:20.017 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:50:20.018 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_UP
2023-02-19 20:50:20.018 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_UP

Down事件会从activity->viewgroup->view,move和up事件都被activity自己消费了,不会进行事件分发,因为之前的down事件没有人分发,就表示子view不会处理点击事件。
事件传输流程:
在这里插入图片描述

  1. 自定义的ViewGroup的onInterceptTouchEvent返回true,但是并不消耗事件,日志如下:
2023-02-19 20:57:04.896 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.898 7618-7618/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.898 7618-7618/com.example.androidstart D/CustomizeLayout: onInterceptTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.898 7618-7618/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.899 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.995 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.996 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.998 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.998 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.999 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_UP
2023-02-19 20:57:04.999 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_UP

Down事件会从Activity->ViewGroup,因为ViewGroup进行了拦截,所以这里不会分发到子View,MOVE和UP事件也只会在activity中进行分发

  1. viewGroup的onInterceptTouchEvent调用父类实现,但是onTouchEvent返回truue,即ViewGroup不拦截但是消耗事件。
2023-02-19 21:17:28.939 9448-9448/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.940 9448-9448/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.941 9448-9448/com.example.androidstart D/CustomizeLayout: onInterceptTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.941 9448-9448/com.example.androidstart I/CustomizeTextView: dispatchTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.942 9448-9448/com.example.androidstart I/CustomizeTextView: onTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.942 9448-9448/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_DOWN
2023-02-19 21:17:29.021 9448-9448/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 21:17:29.022 9448-9448/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_MOVE
2023-02-19 21:17:29.022 9448-9448/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_MOVE
2023-02-19 21:17:29.023 9448-9448/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_UP
2023-02-19 21:17:29.023 9448-9448/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_UP
2023-02-19 21:17:29.023 9448-9448/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_UP

down事件会activity->viewgroup->view,因为viewgroup消耗了事件,因此down事件不会回到activity,同时由于已经有了事件消费者,因此MOVE和UP事件不会再往view进行传递,回直接调用到ViewGroup的onTouch中中。

requestdisallowIntereptTouchEvent作用

子view在自己的down或者move的时候调用requestdisallowIntereptTouchEvent,这样父view在这次事件传递中就不会拦截当前链路的事件。
链接:
requestDisallowInterceptTouchEvent失效的原因及解决

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

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

相关文章

一文让你彻底理解Linux内核多线程(互斥锁、条件变量、读写锁、自旋锁、信号量)

一、互斥锁&#xff08;同步&#xff09; 在多任务操作系统中&#xff0c;同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于&#xff0c;公司部门里&#xff0c;我在使用着打印机打印东西的同时&#xff08;还没有打印完&#xff09;&#xff0c;别人刚好也在…

【Tcp和Udp】

udp和tcpTcpTcp协议的断开与连接Tcp的状态转移复位报文段交互数据流与成块数据流流式服务特点应答确认与超时重传滑动窗口拥塞控制Udp协议特点Tcp Tcp协议的断开与连接 Tcp协议提供的是&#xff1a;面向连接&#xff0c;可靠的&#xff0c;字节流服务。 使用Tcp协议通信的双方…

java封装继承多态详解

1.封装 所谓封装&#xff0c;就是将客观事物封装成抽象的类&#xff0c;并且类可以把数据和方法让可信的类或者对象进行操作&#xff0c;对不可信的类或者对象进行隐藏。类就是封装数据和操作这些数据代码的逻辑实体。在一个类的内部&#xff0c;某些属性和方法是私有的&#…

自学大数据第三天~终于轮到hadoop了

前面那几天是在找大数据的门,其实也是在搞一些linux的基本命令,现在终于轮到hadoop了 Hadoop hadoop的安装方式 单机模式: 就如字面意思,在一台机器上运行,存储是采用本地文件系统,没有采用分布式文件系统~就如我们一开始入门的时候都是从本地开始的; 伪分布式模式 存储采用…

openpnp - 判断吸嘴是否指定了正确的旋转轴

文章目录openpnp - 判断吸嘴座是否指定了正确的旋转轴概述笔记吸嘴单独矫正的时候Calibrate precise camera ↔ nozzle N1 offsets.ENDopenpnp - 判断吸嘴座是否指定了正确的旋转轴 概述 如果没有指定吸嘴座的正确旋转轴, 会因为对应吸嘴该旋转时不旋转, 而是另外一个空闲的吸…

Linux学习记录——십삼 程序地址空间

文章目录1、了解程序地址测试代码2、理解程序地址空间3、程序地址空间存在的意义1、了解程序地址测试代码 1 #include <stdio.h>2 #include <assert.h>3 #include <unistd.h>4 5 int g_value 100;6 int main()7 {8 pid_t id fork();9 assert(id &g…

设计模式之结构型模式

1、代理模式1.1 静态代理1.2 jdk 动态代理1.3 CGLIB 动态代理1.4 优缺点2、适配器模式2.1 类适配器模式2.2 对象适配器模式2.3 JDK 源码解析3、装饰者模式3.1 JDK 源码解析3.2 代理模式与装饰者模式的区别4、桥接模式5、外观模式6、组合模式6.1 组合模式的分类6.2 优点及使用场…

ZincSearch Java 客户端教程

ZincSearch Zinc 简单、强大&#xff0c;不了解的同学可以参见我之前的博客。今天我们这里谈谈 Java 环境如何集成 Zinc 客户端&#xff0c;跟如何使用的。 安装 Zinc 到 Github 的官方 Releases 下载&#xff1a; 我的是 Windows 开发环境&#xff0c;下载 zincsearch_0.4…

基于支持向量机SVM的面部表情分类预测

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 SVM应用实例,基于SVM的面部表情分类预测 代码 结果分析 展望 支持向量机SVM的详细原理 SVM的定义 支持向量机(support vector machines, SVM)是一种二分类模型,它的基本…

【网络】-- 网络编程套接字(铺垫、预备)

目录 理解源IP地址和目的IP地址 认识端口号 端口号 理解源端口号和目的端口号 套接字 认识TCP与UDP协议 网络字节序 socket编程接口 socket 常见API sockaddr结构 理解源IP地址和目的IP地址 就如同我们唐僧的取经路&#xff1a; 唐僧的出发地到目的地&#xff1a;东…

阿尔法开发板 .bin 文件烧写

一. IMX6ULL 开发板简介 IMX6ULL 开发板是正点原子提供的阿尔法开发板&#xff0c;所用芯片为恩智浦&#xff0c;基于 Cortex-A7 架构。 这里介绍一下裸机篇中&#xff0c;关于如何将 .bin 文件烧写进 SD 卡&#xff0c;从而设备运行程序。 二. xx.bin 文件烧写 IMX6ULL支…

句子的改写和扩写

目录 1.句子改写 2.句子扩写 &#xff08;不低于15个句子算是长句子&#xff0c;不能太多长句子&#xff09; 1.句子改写 我绝不会嫁给你的。 如果你是世界上最后一个男人&#xff0c;我就去寺庙。 If you married me,I would jump into the well. 如果你嫁给我&#xff0c;我…

【Java学习笔记】5.Java 基本数据类型

Java 基本数据类型 变量就是申请内存来存储值。也就是说&#xff0c;当创建变量的时候&#xff0c;需要在内存中申请空间。 内存管理系统根据变量的类型为变量分配存储空间&#xff0c;分配的空间只能用来储存该类型数据。 因此&#xff0c;通过定义不同类型的变量&#xf…

计算机网络的166个概念你知道几个 第三部分

广播&#xff1a;我们一般小时候经常会广播体操&#xff0c;这就是广播的一个事例&#xff0c;主机和与他连接的所有端系统相连&#xff0c;主机将信号发送给所有的端系统。 多播&#xff1a;多播与广播很类似&#xff0c;也是将消息发送给多个接收主机&#xff0c;不同之处在于…

最近一年我都干了什么——反思!!

过去一年不管是学习方式还是心态上都和以往有了许多不同的地方&#xff0c;比较昏昏沉沉。最近慢慢找到状态了&#xff0c;就想赶紧记录下来。 学习 在学习新技术的过程中开始飘了&#xff0c;总感觉有了一些开发经验后就觉得什么都不用记&#xff0c;知道思路就行遇到了现场百…

15、正则表达式

目录 一、元字符 二、限定修饰符 一、元字符 正则表达式通常被用于判断语句中&#xff0c;用来检查某一字符串是否满足某一格式。正则表达式是含有一些具有特殊意义字符的字符串&#xff0c;这些特殊字符称为正则表达式的元字符。例如&#xff0c;“\\d”表示数字0~9中的任何…

BUU [ZJCTF 2019]Login

这是一道让我感觉很淦的题&#xff0c;整一天了才大致了解了来龙去脉 开始&#xff1a; 首先丢到虚拟机checksec一下看看有啥保护措施&#xff1a; 看到开了Canary&#xff0c;就已经感觉不妙了&#xff0c;接着丢到IDA里看看啥情况 一看&#xff0c;是令人痛苦的c风格的代码…

IDEA中使用Tomcat的两种方式:集成本地Tomcat使用Tomcat Maven插件

一、前言 在IDEA中创建完一个Maven Web项目&#xff0c;并补齐了目录以后&#xff0c;准备使用Tomcat时&#xff0c;就需要在自己创建的项目中去部署Tomcat&#xff0c;前文已经介绍了如何创建Maven Web&#xff0c;所以这里就不多加赘述&#xff0c;直接讲述部署Tomcat的方法…

fastadmin后台登录页修改

直接替换就行 <!DOCTYPE html> <html lang"{$config.language}"> <head>{include file"common/meta" /}<style type"text/css">body {color: #999;background-color: #f1f4fd;background-size: cover;}a {color: #444;…

[极客大挑战 2019]EasySQL 1

[极客大挑战 2019]EasySQL 1解题POC一、解题思路之暴力破解1. 弱口令2. 暴力破解二、解题思路之万能密码1. 什么是万能密码2. 测试过程解题POC 直接点击登录获取flagflag{62f0d2ca-579e-450e-941f-5f7c23a8baf7} 一、解题思路之暴力破解 这题是万能密码&#xff0c;所以暴力破解…