有些困难无法逃避,没办法,那就只有去解决它。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. 深夜,我用python爬取了整个斗图网站,不服来斗

    QQ.微信斗图总是斗不过,索性直接来爬斗图网,我有整个网站的图,不服来斗. 废话不多说,选取的网站为斗图啦,我们先简单来看一下网站的结构 网页信息 从上面这张图我们可以看出,一页有多套图,这个时候我们 ...

  2. AI学习笔记:人工智能与机器学习概述

    一.人工智能基本概念 1.1 基本概念 数据分析:对历史规律的展现.对未来数据的预测. 机器学习:机器学习是指从一系列的原始数据中找到规律,提取人们可以识别的特征,然后通过学习这些特征,最终产生一个模 ...

  3. 分布式专题——详解Google levelDB底层原理

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是分布式专题的第10篇文章,我们继续来聊聊LSMT这个数据结构. LSMT是一个在分布式系统当中应用非常广泛,并且原理直观简单的数据结构 ...

  4. 【C++】Strassen算法代码

    本文仅代码,无理论解释 实话实说,我觉得这个算法在C系列的语言下,简直垃圾到爆炸--毕竟是一群完全不懂程序数学家对着纸弄出来的,看起来好像非常的有用,实际上耗时是非常爆炸的. 但是<算法导论&g ...

  5. 基础组合问题 ————从n个物品里选m个

    package test; import java.util.*; public class Main{ public static int f(int n,int k, int goal){ if( ...

  6. 5L-链表导论心法

    链表是比数组稍微复杂一点的数据结构,也是两个非常重要与基本的数据结构.如果说数组是纪律严明排列整齐的「正规军」那么链表就是灵活多变的「地下党」. 关注公众号 MageByte,有你想要的精彩内容. 链 ...

  7. 120prop-python3.7 读写.properties文件

    120prop-python3.7 读写.properties文件 转载 nature_ph 最后发布于2019-07-30 10:12:05 阅读数 229 收藏 发布于2019-07-30 10: ...

  8. 移植OPENNI到DM6446上面

    为了利用摄像头的景深信息,同时利用dm6446的分析功能(dsp),对openNI进行USB移植. 下载:libusb-1.0.0.tar.bz2            OpenNI-Stable-1 ...

  9. FastDFTJava客户端使用

    1.1.java客户端 余庆先生提供了一个Java客户端,但是作为一个C程序员,写的java代码可想而知.而且已经很久不维护了. 这里推荐一个开源的FastDFS客户端,支持最新的SpringBoot ...

  10. 用c#判断回文数和降序数

    题目:编一个程序,输入一个正整数,判定它是否为回文数和降序数.当输入的数为0时,则退出程序,否则继续循环执行程序. 所谓“降序数”是指一个自然数的低位数字不大于高位数字的数.例如: 64, 55, 3 ...