再谈事件分发

简介: 再谈事件分发

前言

“大哥大哥,快来,领导叫你过去面试!!”

“来了来了!”

看了看简历,面试的是中级安卓开发。

“你来说一下安卓事件分发机制吧!”

“大哥,老实说,事件分发我也不咋会,还幸亏你面试我的时候高抬贵手,没问我,要问我的话我就进不来咱们公司了!”

“抬啥手,你面试的时候面试的是实习生,我问你这干啥?”

“奥,好吧。。。。话说回来,大哥,你能给我讲讲事件分发嘛?”

“行吧,那就给你从头到尾说一遍吧,这个以后都会有用的。”

“谢谢大哥,我去给大哥泡咖啡!!!”

正文

事件分发,安卓开发中老生常谈的一个问题了,不仅仅是为了应付面试,实际工作中也会经常用到(这里要说明一下,平时尽量要把知识学习的牢固一些,不要等到面试了才临时抱佛脚)。下面我会将事件分发好好扒一扒!

Activity构成

“小子,给我说一下Activity的构成吧!”

“构成?Activity不就是Activity嘛!啥构成啊?”

“。。。。。。记好了。。。。。。”

一个Activity包含了一个Window对象,Window是由PhoneWindow来实现的。PhoneWindow将DecorView作为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域:一个是 TitleView,另一个是ContentView,而我们平时所写的就是展示在ContentView中的。

“好像有点明白了,但是大哥,这和事件分发有什么关系呢?”

“猴急的性子就是改不了,马上要说了!”

事件类型

触摸事件对应的是MotionEvent类,事件的类型主要有以下三种:


  • ACTION_DOWN 在屏幕按下时触发
  • ACTION_MOVE 在屏幕上滑动时触发(移动的距离超过一定的阈值才会被判定为ACTION_MOVE操作)
  • ACTION_UP 在屏幕抬起时触发
  • ACTION_CANCLE 当滑动超出控件边界时触发

“大哥,你刚刚说的阈值到底是多少啊?我记得以前看别人写的博客也这样说的,但一直没搞明白阈值到底是多少?”


Log.i("ViewConfiguration", "TouchSlop=" + ViewConfiguration.get(this).scaledTouchSlop)

“不错不错,小子,求知欲很强嘛!”

TouchSlop(系统常量)表示最小移动阈值,默认值一般为8dp。这个相信都能明白,意思就是如果你滑动小于这个值的话系统就会认为你这不属于滑动事件。这里需要注意,有的手机厂商为了“提高用户体验”,重新设置了这个值的大小,如果不确定可以打印下看看。

下面是执行结果:

2020-05-07 22:12:35.939 12599-12599/com.zj.weather I/ViewConfiguration: TouchSlop=21

“明白了吗?”

“呃呃。。。大哥你继续讲吧,我能听懂!”

其实View事件分发的本质很简单,当一个MotionEvent事件发生后,系统将这个点击事件传递到一个具体的View上,就是对MotionEvent事件分发的过程。

事件分发流程

“小子,来给我说说事件分发过程由哪几个方法完成的!”

“嗯。。。有dispatchTouchEvent,还有onInterceptTouchEvent和onTouchEvent三个方法完成的。”

“不错不错,那你给我说说这三个方法的执行过程吧”

“大哥,你直接说吧,我只知道有这三个方法。。。。”

“好,不为难你了,下面要认真听了。”

咱们就一个一个方法来说吧!

**dispatchTouchEvent(分发):**当方法返回值为true时表示事件被当前视图消费掉;如果返回false时则表示交给父类中的onTouchEvent进行处理;如果返回为super.dispatchTouchEvent时则表示会继续分发该事件。

**onInterceptTouchEvent(拦截):**当方法返回值为true时表示拦截这个事件并交由自身的onTouchEvent方法进行消费;如果返回false则表示不拦截,需要继续传递给子视图。如果返回super.onTouchEvent(ev),这块就有点麻烦了,分为两种情况:

  • 如果该View存在子View且点击到了该子View, 则不拦截, 继续分发给子View 处理, 此时相当于返回 false
  • 如果该View没有子View或者有子View但是没有点击中子View(此时ViewGroup 相当于普通View), 则交由该View的onTouchEvent响应,此时相当于返回true。

这块需要注意的是:LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默认不拦截, 而 ScrollView、ListView等ViewGroup则可能拦截,需要看具体情况。

**onTouchEvent(消费):**方法返回值为true表示当前视图可以处理对应的事件;返回值为false表示当前视图不处理这个事件,它会被传递给父视图的onTouchEvent方法进行处理。如果返回super.onTouchEvent(ev),事件处理又分为了两种情况:

  • 如果该View是clickable或者longclickable的,则会返回true, 表示消费 了该事件, 与返回true一样
  • 如果该View不是clickable或者longclickable的,则会返回false, 表示不 消费该事件,将会向上传递,与返回false一样。

“小子,听懂了吗?所谓的事件分发就这么简单”

“啊?这都啥是啥啊!根本不理解,大哥,你能再好好说说吗?”

“真拿你没办法,行,听好了!”

你看啊,安卓中有事件传递处理能力的也就有Activity、ViewGroup、和View三种,你就想,Activity和View拦截事件干嘛呢?他们又不管,所以他们有分发和消费不就够了嘛。但是ViewGroup不一样啊,它里面会包含有子View,所以才要有拦截事件,没问题吧?算了,给你写一个伪代码吧!

fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        var result = false
        if (onInterceptTouchEvent(ev)) {
            result = onTouchEvent(ev)
        } else {
            result = child.dispatchTouchEvent(ev)
        }
        return result
    }

“小子,这样总行了吧?”

“额。。。。大哥,你再说说吧。。。”

你看上面的代码,对应一个根ViewGroup来说,点击事件产 生后,首先会传递给它,这个时候它的dispatchTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处 理,这时如果它的mOnTouchListener被设置,则onTouch会被调用,否则的话onTouchEvent会被调用。 在onTouchEvent中,如果设置了mOnCLickListener,则onClick会被调用。这里需要注意,只要View的CLICKABLE和 LONG_CLICKABLE有一个为true,onTouchEvent就会返回true消耗这个事件;如果onInterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,就这样一直反复直到事件被最终处理。

“小子,是不是很简单?”

“听你说的很简单,但我还要再好好消化消化!”

“嗯,确实,这块其实很麻烦,简单的还好说,如果多层滑动布局进行嵌套要处理滑动冲突就比较麻烦了。帮你总结一下吧!”

小总结

  • 事件传递优先级:onTouchListener.onTouch > onTouchEvent > onClickListener.onClick
  • ViewGroup默认不拦截任何事件(返回false)
  • View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和 longClickable同时为false)
  • View的longClickable属性默认都为false,clickable属性要分情况,比如 Button的clickable属性默认为true,而TextView的clickable默认为false
  • View的enable属性不影响onTouchEvent的默认返回值

通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是 ACTION_DOWN事件除外

“记着上面这几点,你理解起来会更快一些的!”

“感谢大哥!”

开发中的问题

“对了大哥,ACTION_CANCEL什么时候触发啊,如果触摸button然后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?”

“你能不能一个问题一个问题问。。。。”

  • 一般ACTION_CANCEL和ACTION_UP都作为View一段事件处理的结束。如果在父View中拦截 ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件。
  • 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方),就会出现 ACTION_CANCEL。

“理解了嘛?”

“嗯嗯,这个知道了,但我还有问题,如果点击事件被拦截了,但是我又想传到下面的View,该怎么搞啊?”

“这个简单!只要重写子类的requestDisallowInterceptTouchEvent()方法返回true就不会执行父类的 onInterceptTouchEvent(),就可以将点击事件传到下面的View了。”

“大哥,那我平时遇到的滑动冲突该怎么解决啊?”

“这个说起来就比较麻烦了,大概给你说下思路吧,这个需要你以后开发中慢慢体会!”

滑动冲突的处理规则:

  • 对于由于外部滑动和内部滑动方向不一致导致的滑动冲突,可以根据滑动的方向判断谁来拦截事件。
  • 对于由于外部滑动方向和内部滑动方向一致导致的滑动冲突,可以根据业务需求,规定何时让外部 View拦截事件,何时由内部View拦截事件。
  • 对于上面两种情况的嵌套,相对复杂,可同样根据需求在业务上找到突破点。

滑动冲突的实现方法:

  • 外部拦截法:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦 截。具体方法:需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截。
  • 内部拦截法:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件 就直接消耗,否则就交由父容器进行处理。具体方法:需要配合 requestDisallowInterceptTouchEvent方法

总结

“小子,这样总可以了吧?”

“大哥,我还有问题。。。。”

“放过我吧,这么晚了,有问题明天再说吧!”

本文简单描述了一下安卓的事件分发机制,描述的不是特别详细,大家可以自己写个小Demo试试,加深一下理解。

目录
相关文章
|
3月前
|
机器学习/深度学习
从DQN到Double DQN:分离动作选择与价值评估,解决强化学习中的Q值过估计问题
2015年DQN在Atari游戏中突破,但Q值过估计问题浮现。因max操作放大噪声,智能体盲目自信“黄金动作”。根源在于动作选择与价值评估由同一网络完成,导致最大化偏差。
196 9
从DQN到Double DQN:分离动作选择与价值评估,解决强化学习中的Q值过估计问题
|
1天前
|
人工智能 自然语言处理 Shell
🦞 如何在 Moltbot 配置阿里云百炼 API
本教程指导用户在开源AI助手Clawdbot中集成阿里云百炼API,涵盖安装Clawdbot、获取百炼API Key、配置环境变量与模型参数、验证调用等完整流程,支持Qwen3-max thinking (Qwen3-Max-2026-01-23)/Qwen - Plus等主流模型,助力本地化智能自动化。
🦞 如何在 Moltbot 配置阿里云百炼 API
|
6天前
|
人工智能 API 开发者
Claude Code 国内保姆级使用指南:实测 GLM-4.7 与 Claude Opus 4.5 全方案解
Claude Code是Anthropic推出的编程AI代理工具。2026年国内开发者可通过配置`ANTHROPIC_BASE_URL`实现本地化接入:①极速平替——用Qwen Code v0.5.0或GLM-4.7,毫秒响应,适合日常编码;②满血原版——经灵芽API中转调用Claude Opus 4.5,胜任复杂架构与深度推理。
|
9天前
|
JSON API 数据格式
OpenCode入门使用教程
本教程介绍如何通过安装OpenCode并配置Canopy Wave API来使用开源模型。首先全局安装OpenCode,然后设置API密钥并创建配置文件,最后在控制台中连接模型并开始交互。
4400 8
|
15天前
|
人工智能 JavaScript Linux
【Claude Code 全攻略】终端AI编程助手从入门到进阶(2026最新版)
Claude Code是Anthropic推出的终端原生AI编程助手,支持40+语言、200k超长上下文,无需切换IDE即可实现代码生成、调试、项目导航与自动化任务。本文详解其安装配置、四大核心功能及进阶技巧,助你全面提升开发效率,搭配GitHub Copilot使用更佳。
10114 21
|
2天前
|
人工智能 自然语言处理 Cloud Native
大模型应用落地实战:从Clawdbot到实在Agent,如何构建企业级自动化闭环?
2026年初,开源AI Agent Clawdbot爆火,以“自由意志”打破被动交互,寄生社交软件主动服务。它解决“听与说”,却缺“手与脚”:硅谷Manus走API原生路线,云端自主执行;中国实在Agent则用屏幕语义理解,在封闭系统中精准操作。三者协同,正构建AI真正干活的三位一体生态。
2211 9
|
1天前
|
存储 安全 数据库
使用 Docker 部署 Clawdbot(官方推荐方式)
Clawdbot 是一款开源、本地运行的个人AI助手,支持 WhatsApp、Telegram、Slack 等十余种通信渠道,兼容 macOS/iOS/Android,可渲染实时 Canvas 界面。本文提供基于 Docker Compose 的生产级部署指南,涵盖安全配置、持久化、备份、监控等关键运维实践(官方无预构建镜像,需源码本地构建)。
922 2
|
17小时前
|
机器人 API 数据安全/隐私保护
只需3步,无影云电脑一键部署Moltbot(Clawdbot)
本指南详解Moltbot(Clawdbot)部署全流程:一、购买无影云电脑Moltbot专属套餐(含2000核时);二、下载客户端并配置百炼API Key、钉钉APP KEY及QQ通道;三、验证钉钉/群聊交互。支持多端,7×24运行可关闭休眠。
|
17天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
2545 18