有些困难无法逃避,没办法,那就只有去解决它。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事件分发源码理解的更多相关文章

  1. Android View事件分发源码分析

    引言 上一篇文章我们介绍了View的事件分发机制,今天我们从源码的角度来学习下事件分发机制. Activity对点击事件的分发过程 事件最先传递给当前Activity,由Activity的dispat ...

  2. ViewGroup事件分发源码分析

    1.AndroidStudio源码调试方式 AndroidStudio默认是支持一部分源码调试的,但是build.gradle(app) 中的sdk版本要保持一致, 最好是编译版本.运行版本以及手机的 ...

  3. Qt中事件分发源码剖析

    Qt中事件分发源码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,而且进行分发,这些都是在exec中进行的. 以下举例说明: 1)首先看看以下一段演示样例代码: ...

  4. android事件分发源码分析—笔记

    昨天晚上从源码角度复习了一下android的事件分发机制,今天将笔记整理下放在网上.其实说复习,也是按着<android开发艺术探索>这本书作者的思路做的笔记. 目录 事件是如何从Acti ...

  5. Touch事件分发源码解析

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 以下源码基于Gingerbread 2.3.7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.先看ViewGroup的di ...

  6. 深入理解Spring系列之十:DispatcherServlet请求分发源码分析

    转载 https://mp.weixin.qq.com/s/-kEjAeQFBYIGb0zRpST4UQ DispatcherServlet是SpringMVC的核心分发器,它实现了请求分发,是处理请 ...

  7. View事件体系

    View事件体系 文章目录 View事件体系 一.Android View基础知识 1.1 View简介 1.2 View分类 1.3 View的结构 1.4 View的坐标 1.4.1 Androi ...

  8. Android View事件机制一些事

    本文主要讲述: 自己对View事件机制的一些理解 在项目中遇到的一些坑,解决方案 收集了一些View的事件机制问题 事件的分发原理图 对于一个root viewgroup来说,如果接受了一个点击事件, ...

  9. Android View事件分发-从源码分析

    View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ...

随机推荐

  1. 使用burp插件captcha-killer识别图片验证码

    0x01 开发背景 说起对存在验证码的登录表单进行爆破,大部分人都会想到PKav HTTP Fuzzer,这款工具在前些年确实给我们带来了不少便利.反观burp一直没有一个高度自定义通杀大部分图片验证 ...

  2. Chrome80调整SameSite策略对IdentityServer4的影响以及处理方案(翻译)

    首先,好消息是Goole将于2020年2月份发布Chrome 80版本.本次发布将推进Google的"渐进改良Cookie"策略,打造一个更为安全和保障用户隐私的网络环境. 坏消息 ...

  3. 基础的linux命令(一)

    我练习使用的 Linux 系统是 CentOS 7 它是通过把 RHEL 系统重新编译并发布给用户免费使用的 Linux 系统. 首先你需要一台Linux虚拟机,如果没有,也没关系,点这里 一.命令格 ...

  4. 你需要了解的 HTTP Status Code

    你需要了解的 HTTP Status Code Intro 现在前后端分离的开发模式越来越流行,后端负责开发对应的 API,前端只需要 关注前端页面的数据展示和前端逻辑即可. 对于前后端分离这种开发模 ...

  5. 证明与计算(7): 有限状态机(Finite State Machine)

    什么是有限状态机(Finite State Machine)? 什么是确定性有限状态机(deterministic finite automaton, DFA )? 什么是非确定性有限状态机(nond ...

  6. PyTorch1.2.0版本来啦!居然还有全套视频!让你快速熟练掌握深度学习框架!

    [翻到文末, 还能让你看尽CV和NLP完整技术路径以及前沿+经典论文篇目,助你构建深度学习知识框架] 今年8月!PyTorch 1.2.0 版本来啦!! 据我们了解,在学术领域,特别是CV/NLP方向 ...

  7. Go语言库系列之dotsql

    导读:能单独拎出SQL文件的某一行或几行执行,是不是非常有趣?今天我们来介绍一下这个有意思的库--dotsql. 背景介绍 dotsql不是ORM,也不是SQL查询语句的构建器,而是可以在一个SQL文 ...

  8. Centos 8 上安装 Consul

    /* 1. 下载二进制安装文件 */下载地址:https://www.consul.io/downloads.html /* 2. 解压缩安装包 */unzip consul_1.6.2_linux_ ...

  9. Chrome 经典插件

    记录几个很喜欢的 Chrome 插件,怕之后找不到了. 1. Dark Theme 很喜欢的一个黑色主题! 2. Volume Booster 能把音量提高2倍的小插件!好用! 3. Looper f ...

  10. 感知器基础原理及python实现

    简单版本,按照李航的<统计学习方法>的思路编写 数据采用了著名的sklearn自带的iries数据,最优化求解采用了SGD算法. 预处理增加了标准化操作. ''' perceptron c ...