view事件分发源码理解
有些困难无法逃避,没办法,那就只有去解决它。view事件分发对我而言是一块很难啃的骨头,看了《安卓开发艺术探索》关于这个知识点的讲解,看了好几遍,始终不懂,最终通过调试分析结果,看博客,再回过头看,总算能了解个大概。真的只能说大概,因为我在理解的过程中,还是会刻意忽略掉不少我不懂的又会诱导我深入分析的知识点,这些知识点就像歧路亡羊中的歧路,当我在不断分叉的歧路中走的越远,我离要找的羊也就越远,羊就是对整个分发体系的整体把控。就说到这了,开始探索之旅吧!
1.事件从Activity如何分发至主布局xml文件生成的view树。
- //所有触摸事件先从activity中的这个方法开始。
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- //这是个空方法,不用管
- onUserInteraction();
- }
- //第一步:
- //所以事件的分发都先从底下的这个if判断中展开,这个方法必走,注意先会让Activity附属的window进行分发,
- // 如果返回true,那么事件循环结束
- //如果返回false,那么就调用底下的onTouchEvent.这里重点要看wondow是怎么把事件分发下去的。
- //window作为抽象类,从它的注释介绍中得知它的唯一实现类是PhoneWindow,接着看这个类的superDispatchTouchEvent
- if (getWindow().superDispatchTouchEvent(ev)) {
- return true;
- }
- return onTouchEvent(ev);
- }
以下是PhoneWindow中的superDispatchTouchEvent()方法
- //第二步:
- //phonewindow将事件传递给了mDecor,他是类的成员变量。位于144行,接着看DecorView
- @Override
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return mDecor.superDispatchTouchEvent(event);
- }
以下是DecorView中的superDispatchTouchEvent()方法
- //第三步:DecorView继承FrameLayout,FrameLayout又是继承于ViewGroup,
- // 这个FrameLayout就是我们日常写的activity的父布局,在ViewGroup中重写了
- //dispatchTouchEvent,快去看看
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return super.dispatchTouchEvent(event);
- }
DecorView是一个FrameLayout布局,它由上下两部分组成,上面是actionBar,下面是我们最最亲爱的在setContentView()方法中传进xml布局文件,生成的视图组。上面的事件已经分发至DecorView了,现在我们将样式设为为noActionBar,那么DecorView布局中就只有一个contentView布局。在上面的方法中,由于FrameLayout没有重写分发方法,所以会接着向上查找分发方法,最终找到ViewGroup中的dispatchTouchEvent方法,而这个viewGroup中的第一个子view就是contentView生成的视图组。接下来看看viewGroup中的分发方法。说实话,前面铺垫了那么多,完成可以当课外知识了解,毕竟我们经常会默认事件分发就是从contentView这个视图组进行分发的。好,来看重头戏:(为了减轻阅读压力,以下是阉割版)
- TouchTarger mFirstTouchTarget = null;
- //每一个MotionEvent包括一个dowm,n个move,一个up,这一系列的action,都会触发这个方法,
- //所以这个方法被调用的次数就是(1+n+1)
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean handled = false;
- boolean intercepted = false;
- //第一步:判断viewGroup自己是否要拦截,如果不拦截一般存在两种情况:
- //1.用户的第一个动作是按下
- //2.mFirstTouchTarget有值,从后面得知,当前的viewGroup中的某个子view处理了这个事件,mFirstTouchTarget才被附上值。
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- //不重写的话,onInterceptTouchEvent默认返回false
- intercepted = onInterceptTouchEvent(ev);
- } else {
- intercepted = true;
- }
- //第二步:viewGroup自己不拦截,就把事件下发给它的子view
- if (!intercepted){
- //遍历viewGroup底下的所有子view
- for (int i = childrenCount - 1; i >= 0; i--) {
- //省略代码:如果子view不再当前点击区域内,continue;
- //能走到这里的,表示子view都在点击区域内,所以事件能传递给它
- //这里也分两种情况:
- //1.child为viewGroup,就在这将整个方法重调一次
- //2.child 为view,view重写了分发方法,代码在后面会贴出,先提一下它的特征:
- //view没有子元素下发,所以它的dispatchTouchEvent就只是处理事件。
- if (child.dispatchTouchEvent()) {
- //能走到这里的,表示事件被处理。
- mFirstTouchTarget = object;
- break;
- }
- }
- }
- //第三步:viewGroup自己处理,分两种情况:
- //1.intercepted = true;表示一开始viewGroup就决定自己处理
- //2.intercepted = false,结果遍历完子元素他们都没处理,导致mFirstTouchTarget = null
- if (mFirstTouchTarget == null) {
- //由于viewGroup继承view,所以这里也是调用view的分发方法,来处理事件。
- handled = super.dispatchTouchEvent(ev);
- }
- return handled;
- }
上面的精简代码,多看几遍,相信你能了解viewGroup对motionEvent的分发有个清晰的了解。已经可以看到不管事件如何传递,最终都会调用到view.dispatchTouchEvent方法,我们在看看它的逻辑。先来个定心丸,最难的代码就是上面那部分,接下来的都是小菜:
- //view
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean result = false;
- //当前view是否设置了OnTouchListener,以及监听事件中的onTouch()
- //是否返回ture
- if (li.mOnTouchListener != null
- && li.mOnTouchListener.onTouch(this, event)) {
- result = true;
- }
- //如果上面的if成立,那就没这个if什么事了。所以onTouchEvent也就不会调用。
- //当然,我们也要看看result=false;进入onTouchEvent瞅一瞅:
- if (!result && onTouchEvent(event)) {
- result = true;
- }
- return result;
- }
- public boolean onTouchEvent(MotionEvent ev){
- boolean clickable = 可点击 or 可长按;
- if (clickable) {
- switch (action){
- //从这看出,我们最常用的clickListenr在手指抬起才会触发
- case up:
- if (li != null && li.mOnClickListener != null) {
- li.mOnClickListener.onClick(this);
- }
- break;
- case down:
- //
- break;
- //其他情况
- }
- //这个return true很容易被人忽视,也是说只要可点击,最终onTouchEvent都会消耗这个事件
- return true;
- }
- return false;
- }
纵观view的分发方法,说白了就是处理MotionEvent,而且view也没有onInterceptTouchEvent()方法。至此,源代码解析完了,我们看用个实例来增加理解。我们自定义一个LinearLayout和Button。
- public class MyLayout extends LinearLayout {
- private static final String TAG = "DispatchActivity";
- public MyLayout(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.d(TAG,"layout "+MotionEvent.actionToString(ev.getAction()));
//这里的return值会做修改- return true;
- }
- }
Button:
- public class MyButton extends Button {
- private static final String TAG = "DispatchActivity";
- public MyButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.d(TAG, "button: "+MotionEvent.actionToString(event.getAction()));
//这里的return值会做修改- return true;
- }
- }
xml布局
- <com.lq.testlayoutinflate.dispatchevent.MyLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.lq.testlayoutinflate.dispatchevent.MyButton
- android:id="@+id/btn_DispatchActivity"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="click me"/>
- </com.lq.testlayoutinflate.dispatchevent.MyLayout>
我们要测的分3种情况:1.MyLayout 的dispatchTouchEvent()返回ture,Button的 dispatchTouchEvent()分别返回true,false,super.dispatchTouchEvent();
前面说过,我们的activity生成的contentView是添加到FrameLayout中的:所以第开始的分发是从FrameLayout的父类viewGroup开始分发的。当我们在按钮上按钮,然后进行移动,在抬起手指,这是的log日志:这里的3种情况日志是一样的
layout ACTION_DOWN
layout ACTION_MOVE
layout ACTION_MOVE
layout ACTION_MOVE
layout ACTION_UP
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean handled = false;
- boolean intercepted = false;
//以1起始为第一次进入这个方法:- //1.0:第一个动作按下,这里符合,进入if
- //2.0:mFirstTouchTarget有了值,再次进入if
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- //onInterceptTouchEvent默认返回false。
- intercepted = onInterceptTouchEvent(ev);
- } else {
- intercepted = true;
- }
- //1.1:走到这,说明FramaLayout没有拦截:
- //2.1:接下来的和上一次情况类似,继续输出不同动作的log
- if (!intercepted){
- //1.2:FrameLayout就只有一个子节点,即contentView,也就是我们xml的根布局MyLayout
- for (int i = childrenCount - 1; i >= 0; i--) {
- //1.3: MyLayout输出layout ACTION_DOWN日志,然后返回true,为mFirstTouchTarget附上了值。
- if (child.dispatchTouchEvent()) {
- mFirstTouchTarget = object;
- break;
- }
- }
- }
- if (mFirstTouchTarget == null) {
- handled = super.dispatchTouchEvent(ev);
- }
- return handled;
- }
可以看到,父布局MyLayout重写了分发方法,也就是它处理了事件,所以mFirstTouchTarget指向了它。button没有得到分发事件。
第二种情况:
1.MyLayout 的dispatchTouchEvent()返回false,Button的 dispatchTouchEvent()分别返回true,false,super.dispatchTouchEvent();3种情况下的log日志始终为:
layout ACTION_DOWN.这时的日志与上次有所不同,它的move和up事件都没有传递到MyLayout.这是怎么回事,我们再来分析下:
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean handled = false;
- boolean intercepted = false;
- //1.0:第一个动作按下,这里符合,进入if
- //2.0:mFirstTouchTarget==null,actionMasked ==move or up,使得进入else
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- //onInterceptTouchEvent默认返回false。
- intercepted = onInterceptTouchEvent(ev);
- } else {
- intercepted = true;
- }
- //1.1:走到这,说明FramaLayout没有拦截:
- //2.1 intercepted = true;不进入if
- if (!intercepted){
- //1.2:FrameLayout就只有一个子节点,即contentView,也就是我们xml的根布局MyLayout
- for (int i = childrenCount - 1; i >= 0; i--) {
- //1.3: MyLayout输出layout ACTION_DOWN日志,然后返回false,所以mFirstTouchTarget依旧为null。
- if (child.dispatchTouchEvent()) {
- mFirstTouchTarget = object;
- break;
- }
- }
- }
- if (mFirstTouchTarget == null) {
- //1.4:调用:view.dispatchTouchEvent处理事件,由于这个方法我们未重写,也不会有log输出
- //2.2同上,无log输出
- handled = super.dispatchTouchEvent(ev);
- }
- return handled;
- }
第三种情况:
1.MyLayout 的dispatchTouchEvent()返回super.dispatchTouchEvent(),Button的 dispatchTouchEvent()分别返回true,false,super.dispatchTouchEvent();3种情况下的log日志分别为:
layout ACTION_DOWN
button: ACTION_DOWN
layout ACTION_MOVE
button: ACTION_MOVE
layout ACTION_MOVE
button: ACTION_MOVE
layout ACTION_UP
button: ACTION_UP
---------------------------------
layout ACTION_DOWN
button: ACTION_DOWN
-------------------------------
layout ACTION_DOWN
button: ACTION_DOWN
layout ACTION_MOVE
button: ACTION_MOVE
layout ACTION_MOVE
button: ACTION_MOVE
layout ACTION_UP
button: ACTION_UP
(1)第一种情况分析:dispatchTouchEvent()返回super.dispatchTouchEvent(),Button的 dispatchTouchEvent()返回true
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean handled = false;
- boolean intercepted = false;
- //1.0:FrameLayout第一个动作按下,这里符合,进入if
- //2.0 MyLayout第一个动作按下,这里符合,进入if
- //3.0 接下来的动作时move,由于mFirstTouchTarget!=null,所以进入if
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- //onInterceptTouchEvent默认返回false。
- intercepted = onInterceptTouchEvent(ev);
- } else {
- intercepted = true;
- }
- //1.1:走到这,说明FramaLayout没有拦截:
- //2.1:走到这,说明MyLayout没有拦截:
- //3.1:接下来的情况同上。
- if (!intercepted){
- //1.2:FrameLayout就只有一个子节点,即contentView,也就是我们xml的根布局MyLayout
- //2.2:MyLayout 就只有一个子节点,即MyButton。
- for (int i = childrenCount - 1; i >= 0; i--) {
- //1.3: MyLayout输出layout ACTION_DOWN日志,然后返回super.dispatchTouchEvent,
- //现在进入下一个方法块2.0,(执行完2.4再看这里,接受到2.4返回的false,由于只有button这一个子节点,循环结束,这次执行1.4)。
- //2.3: MyButton输出button ACTION_DOWN日志,然会返回true。之后,mFirstTouchTarget被附上了值。
- if (child.dispatchTouchEvent()) {
- mFirstTouchTarget = object;
- break;
- }
- }
- }
- //1.4:直接返回handled,无log日志输出。
- //2.4: handled = false,返回至1.3处,
- if (mFirstTouchTarget == null) {
- handled = super.dispatchTouchEvent(ev);
- }
- return handled;
- }
(2)第二种情况分析:dispatchTouchEvent()返回super.dispatchTouchEvent(),Button的 dispatchTouchEvent()返回false
其实这种情况与第一种情况有两处注释修改一下,读者应该就一目了然了。
- //2.3: MyButton输出button ACTION_DOWN日志,然会返回false。之后,mFirstTouchTarget仍未null。
- //3.0 接下来的动作时是move,并且mFirstTouchTarget==null,所以不进入if,intercepted = true。
- (3)第三种情况分析:dispatchTouchEvent()返回super.dispatchTouchEvent(),Button的 dispatchTouchEvent()返回super.dispatchTouchEvent()。
从log日志种可以看到与第一种情况是一样的。也就是说button分发方法中的 return super.dispatchTouchEvent()默认就是返回ture。为啥这样,看看代码你就会明白。首先我们先回到
注释2.3处:
//MyButton输出button ACTION_DOWN日志,然会返回return super.dispatchTouchEvent()。
这时就会调用view.dispatchTouchEvent()
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean result = false;
- //由于MyButton没有设置点击事件,所以这里的if不满足
- if (li.mOnTouchListener != null
- && li.mOnTouchListener.onTouch(this, event)) {
- result = true;
- }
- //result=false;如果onTouchEvent也返回ture,那结果不就和return true如出一辙了。
- if (!result && onTouchEvent(event)) {
- result = true;
- }
- return result;
- }
读者可以再回头看看上面的onTouchEvent()方法,在这个方法中,这要view是可以点击的,那么这个方法默认就会返回true。在该方法的最后一行注释中,我特地提到过,就是它太容易被人忽视了。
到这,例子的9种子情况都分析完了,代码不多,注释倒写了一箩筐,主要要帮助各位理解。毕竟程序员最讨厌的两件事就是写代码时加注释和看没有注释的代码。还有关于怎么处理事件分发,我没有体到,我认为只有你懂得了以上这些基本分发流程,再去看别人怎么解决滑动冲突的代码,脑子就不会那么迷茫了。
view事件分发源码理解的更多相关文章
- Android View事件分发源码分析
引言 上一篇文章我们介绍了View的事件分发机制,今天我们从源码的角度来学习下事件分发机制. Activity对点击事件的分发过程 事件最先传递给当前Activity,由Activity的dispat ...
- ViewGroup事件分发源码分析
1.AndroidStudio源码调试方式 AndroidStudio默认是支持一部分源码调试的,但是build.gradle(app) 中的sdk版本要保持一致, 最好是编译版本.运行版本以及手机的 ...
- Qt中事件分发源码剖析
Qt中事件分发源码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,而且进行分发,这些都是在exec中进行的. 以下举例说明: 1)首先看看以下一段演示样例代码: ...
- android事件分发源码分析—笔记
昨天晚上从源码角度复习了一下android的事件分发机制,今天将笔记整理下放在网上.其实说复习,也是按着<android开发艺术探索>这本书作者的思路做的笔记. 目录 事件是如何从Acti ...
- Touch事件分发源码解析
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 以下源码基于Gingerbread 2.3.7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.先看ViewGroup的di ...
- 深入理解Spring系列之十:DispatcherServlet请求分发源码分析
转载 https://mp.weixin.qq.com/s/-kEjAeQFBYIGb0zRpST4UQ DispatcherServlet是SpringMVC的核心分发器,它实现了请求分发,是处理请 ...
- View事件体系
View事件体系 文章目录 View事件体系 一.Android View基础知识 1.1 View简介 1.2 View分类 1.3 View的结构 1.4 View的坐标 1.4.1 Androi ...
- Android View事件机制一些事
本文主要讲述: 自己对View事件机制的一些理解 在项目中遇到的一些坑,解决方案 收集了一些View的事件机制问题 事件的分发原理图 对于一个root viewgroup来说,如果接受了一个点击事件, ...
- Android View事件分发-从源码分析
View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ...
随机推荐
- Kubernetes实战总结 - 系统初始化
设置系统主机名以及Host文件的相互解析 hostnamectl set-hostname k8s-master01 cat >> /etc/hosts <<EOF 192.1 ...
- NIO的原理和文件读入读出及图片拷贝的使用
1.NIO的简介 java.nio 全称 java non-blocking IO 是jdk1.4之后出现的 New IO 为所有的原始类型(boolean除外)提供了缓存 ...
- Java爬取丁香医生疫情数据并存储至数据库
1.通过页面的url获取html代码 // 根URL private static String httpRequset(String requesturl) throws IOException { ...
- python报错:ERROR: No matching distribution found for dns.resolver
可能有的小伙伴在安装dns.resolver的时候会遇到这个问题: 我百度的时候别人是: pip install dns-python 但是我这样安装也还是错误.有些时候是这个包改名了所以你没有搜索到 ...
- 动态规划-Minimum Insertion Steps to Make a String Palindrome
2020-01-05 11:52:40 问题描述: 问题求解: 好像多次碰到类似的lcs的变种题了,都是套上了回文的壳.这里再次记录一下. 其实本质就是裸的lcs,就出结果了. public int ...
- leetcode 签到 836. 矩形重叠
836. 矩形重叠 矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标. 如果相交的面积为正,则称两矩形重叠.需要明确的 ...
- [Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted
字面意思大概就是: [干预]忽略尝试取消带有cancelable = false的touchmove事件的尝试,例如,因为滚动正在进行并且无法中断. 解决方法: 1.添加样式更改 将滑动报错的标签样式 ...
- linggle使用技巧
Linggle 搜索引擎是一个可用于英语写作的语法.句子工具,可帮助学习者分析更准确的英文写作建议,能够根据词性来推测短句和句子,可精准的分享出完整英文句子如何撰写. Linggle 是台湾学术团队研 ...
- JavaScript语法记要
JavaScript语法记要 1.JS代码忽略缩进和换行 2.JS六种数据类型 String // 字符串 Number // 数值 Boolean // 布尔值 null // 空值 undefin ...
- GANs和低效映射
生成对抗网络(GANs)被誉为生成艺术领域的下一纪元,这是有充分理由的.新技术一直是艺术的驱动因素,从颜料的发明到照相机再到Photoshop-GAN是自然而然的.例如,考虑下面的图片,由埃尔加马勒发 ...