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 ...
随机推荐
- Linux你不知道的ping操作
1.指定ping的次数 -c 选项 ping -c 3 www.baidu.com 2.只返回结果 -q 选项 ping -q -c 3 www.baidu.com 3.指定数据包的大小 -s ...
- dijkstra模板题 洛谷1339 邻接图建边
题目链接:https://www.luogu.com.cn/problem/P1339 朴素dijkstra算法的复杂度是O(n^2),用堆优化的dijkstra复杂度是O(nlogn)的.在本题中前 ...
- 热点 | 四月最佳Github项目库与最有趣Reddit热点讨论
来源:Analytics Vidhya 编译:磐石 [磐创AI导读]:Github是全球最大的开源代码社区,Reddit是最受大家欢迎的热点讨论交流平台.接下来磐创AI将为大家带来四月份Github最 ...
- 从零开始实现穿衣图像分割完整教程(附python代码演练)
时装业是人工智能领域很有前景的领域.研究人员可以开发具有一定实用价值的应用.我已经在这里展示了我对这个领域的兴趣,在那里我开发了一个来自Zalando在线商店的推荐和标记服装的解决方案. 在这篇文章中 ...
- Git 处理换行符的配置方法
core.autocrlf If you're programming on Windows and working with people who are not (or vice-versa), ...
- 「MoreThanJava」当大学选择了计算机之后应该知道的
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- Faiss向量相似性搜索
Faiss 快速入门(1) Faiss 更快的索引(2) Faiss低内存占用(3) Faiss 构建: clustering, PCA, quantization(4) 如何选择Faiss索引(5)
- Java基础语法(11)-面向对象之关键字
title: Java基础语法(11)-面向对象之关键字 blog: CSDN data: Java学习路线及视频 1.this this是什么 它在方法内部使用,即这个方法所属对象的引用: clas ...
- 角色移动优化【Unity2D自学之路】
自学unity2D独立游戏开发,第一篇自学笔记.在场景中添加角色,并给角色添加Rigidbody2D刚体组件.collection2D碰撞体组件,c#脚本组件控制人物移动和跳跃.c#脚本组件内容如下, ...
- 牛客寒假基础集训营 | Day1 E-rin和快速迭代(暴力 + 优化)
E-rin和快速迭代 题目描述 rin最近喜欢上了数论. 然而数论实在太复杂了,她只能研究一些简单的问题. 这天,她在研究正整数因子个数的时候,想到了一个"快速迭代"算法.设 f( ...