版权声明:本文出自汪磊的博客,转载请务必注明出处。

在前两篇我们共同探讨了事件传递机制《View篇》《ViewGroup篇》,我们知道View触摸事件是ViewGroup传递过去的,比如一个很简单的布局最外层是LinearLayout,里面就一个Button,我们点击Button的时候触摸事件是由外层LinearLayout传递给里面Button的,但是有没有想过当前触摸事件是谁传递给外层的LinearLayout的呢?带着这个疑问我们继续来共同探讨一下。

从Demo示例说起

我们还是先写一个简单的demo,很简单,代码如下:自定义Button:

 public class MyButton extends Button {

     private final String TAG = "WL";

     public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//
Log.i(TAG, "MyButton_dispatchTouchEvent_Action:"+ev.getAction());
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
//
Log.i(TAG, "MyButton_onTouchEvent_Action:"+event.getAction());
return super.onTouchEvent(event);
}
}

自定义LinearLayout:

 public class MyLinearLayout extends LinearLayout {

     private final String TAG = "WL";

     public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//
Log.i(TAG, "MyLinearLayout_dispatchTouchEvent_Action:"+ev.getAction());
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
//
Log.i(TAG, "MyLinearLayout_onTouchEvent_Action:"+event.getAction());
return super.onTouchEvent(event);
}
}

布局文件:

 <com.wl.activitydispatchtouchevent.MyLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
android:id="@+id/mylinearlayout"
android:gravity="center"
tools:context="com.wl.activitydispatchtouchevent.MainActivity" > <com.wl.activitydispatchtouchevent.MyButton
android:id="@+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="WL_Button" /> </com.wl.activitydispatchtouchevent.MyLinearLayout>

Activity中代码:

 public class MainActivity extends Activity implements OnClickListener,
OnTouchListener { private final String TAG = "WL"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
//
findViewById(R.id.mybutton).setOnClickListener(this);
findViewById(R.id.mybutton).setOnTouchListener(this);
//
findViewById(R.id.mylinearlayout).setOnClickListener(this);
findViewById(R.id.mylinearlayout).setOnTouchListener(this);
} @Override
public boolean onTouch(View v, MotionEvent event) {
//
Log.i(TAG, "onTouch___v:" + v + "___action:" + event.getAction());
return false;
} @Override
public void onClick(View v) {
//
Log.i(TAG, "onClick___v:" + v);
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "MainActivity__dispatchTouchEvent__action:" + ev.getAction());
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "MainActivity___onTouchEvent___action=" + event.getAction());
return super.onTouchEvent(event);
}
}

怎么样,很简单吧。和上一篇讲解ViewGroup传递机制的Demo几乎差不多,主要差别就是在Activity中我们重写了Activity的dispatchTouchEvent与onTouchEvent方法。

我们看一下运行效果,点击Button,打印信息如下:

 MainActivity__dispatchTouchEvent__action:0
MyLinearLayout_dispatchTouchEvent_Action:0
MyButton_dispatchTouchEvent_Action:0
onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:0
MyButton_onTouchEvent_Action:0
MainActivity__dispatchTouchEvent__action:1
MyLinearLayout_dispatchTouchEvent_Action:1
MyButton_dispatchTouchEvent_Action:1
onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:1
MyButton_onTouchEvent_Action:1
onClick___v:com.wl.activitydispatchtouchevent.MyButton

除去与Activity有关的信息,其余信息打印顺序相信你应该轻松理解了。我们看到触摸事件实现传递到Activity中的,其次才传递到MyLinearLayout,最后传递给MyButton。是不是触摸事件就是Activity先获取到接下来才继续向下传递的呢?别急着下结论,我们看看Activity中dispatchTouchEvent都做了什么。

Activity事件传递机制源码分析(源码版本为API23

Activity中dispatchTouchEvent方法源码如下:

  public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

是不是爽歪歪?这么短,我们看2-4行代码,首先判断如果是ACTION_DOWN事件则执行onUserInteraction()方法,对于onUserInteraction()方法这里不做具体分析,不是本篇重点。

我们继续向下分析,5-9代码,如果if条件成立则直接返回true,不成立则dispatchTouchEvent最终返回值由onTouchEvent决定,那么if判断就是关键了。

5行代码,getWindow()返回mWindow对象,在Activity的attach方法中进行的初始化,如下:

     final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) { ...........
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
...........
}
mWindow其实就是PhoneWindow对象,接下来我们找到PhoneWindow类(源码目录:...\sdk\sources\android-23\com\android\internal\policy\)。
PhoneWindow类继承自Window类,我们先看看父类中superDispatchTouchEvent是怎么处理的。
Window类中superDispatchTouchEvent源码如下:
1  /**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);

看到了吧,很简单,父类中就是一个抽象方法, 看注释就知道此方法主要用来屏幕事件传递的,开发者不需要实现或者调用这个方法。

接下来我们看看PhoneWindow类中的superDispatchTouchEvent方法:

  @Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

是不是更简单?直接调用mDecor的superDispatchTouchEvent方法,mDecor是什么玩意呢?这里就直说说了,mDecor是DecorView的实例。

DecorView类是PhoneWindow类的内部类,源码如下:

 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

    ..........
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
} ..........
}

我勒个去,搞半天DecorView 继承自FrameLayout,我们知道 FrameLayout继承自ViewGroup,最终就是调用ViewGroup中的dispatchTouchEvent方法进行事件分发。

但是到这里我们还有一个疑问,以我们Demo为例,通过上述分析事件先传递到Activity的dispatchTouchEvent方法,然后调用DecorView 的superDispatchTouchEvent方法最终调用的ViewGroup的dispatchTouchEvent方法,但是跟我们Demo中的MyLinearLayout有什么关系呢?或者说是怎么传递到MyLinearLayout的呢?

要解答这个疑问我们就必须熟知我们平时调用Activity中的setContentView方法设置布局的时候我们自己的布局到底是怎么挂载到Activity上的,这篇我们就不进入深入源码解析了,不是本篇重点,直说一些结论性东西。后续会单独写一篇文章专门分析setContentView究竟都做了什么。

我们在调用setContentView设置布局的时候其实都是被放置在id为content的FrameLayout 布局中的,注意id为content的FrameLayout 布局并不是上面讲的DecorView,具体层级关系如下:

看到了吧,id为content的FrameLayout 布局是DecorView的子View布局。我们自己的布局最后总会替换掉id为content的FrameLayout 。

到这里你该明白了吧,Activity将触摸事件经过层层传递给DecorView, 而DecorView会调用ViewGroup的dispatchTouchEvent方法将事件传递给子View。之后的逻辑就是我们上两篇所讲的内容了。

接下来我们回看Activity中dispatchTouchEvent方法,第5行根据我们上述分析的,如果最终找到子View消耗事件则返回值为true,进而整个方法返回true。如果没有子View处理当前触摸事件则返回false,执行Activity中onTouchEvent方法。

我们接下来分析一下Activity中onTouchEvent方法,源码如下;

 /**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
} return false;
}

逻辑也不复杂,主要就是第12行代码,调用mWindow的shouldCloseOnTouch方法,如果此方法返回true则整个方法返回true,反之则返回false。

mWindow上面我们分析过就是PhoneWindow的实例,好了我们就去PhoneWindow类中找shouldCloseOnTouch方法看一下吧,然而PhoneWindow中并没有这个方法,那我们看看父类Window中有没有这个方法呢,果然这个方法在其父类中找到,源码如下:

 public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}

这里主要逻辑也是一个判断,判断mCloseOnTouchOutside标记以及是否为ACTION_DOWN事件,然后判断点击事件的坐标x,y有没有超出边界,最后调用peekDecorView()判断是否为空。peekDecorView()在Window类中就是一个抽象方法,具体实现在PhoneWindow类中如下:

     public final View peekDecorView() {
return mDecor;
}

很简单,就是返回mDecor,上面我们分析过mDecor就是DecorView的实例,这里我们需要知道我们在Activity中调用setContentView的时候mDecor就会初始化,具体分析会在下一篇文章中,这里只要知道mDecor不为null就可以了。

其余的都不难理解但是mCloseOnTouchOutside 是个什么鬼呢?我们知道Activity设置成Dialog样式的时候默认点击外部的时候是会关闭的,同样我们也可以调用setFinishOnTouchOutside(false)设置为点击外部时候Activity不关闭,mCloseOnTouchOutside 就是用来记录这个的,如果我们将Activity设置为Dialog样式mCloseOnTouchOutside 默认就被设置为true,我们知道大部分情况下Activity是不会设置为Dialog样式的,所以mCloseOnTouchOutside 默认为false。(关于mCloseOnTouchOutside其实是想从源码角度分析一下的,但是这部分内容实在和传递机制不沾边,就这部分有一个判断,所以就不仔细分析了,在下一篇分析setContentView的时候在提一下吧 )

这里我们稍微总结一下:mCloseOnTouchOutside 默认情况下是false,如果Activity样式设置为Dialog系统默认会将mCloseOnTouchOutside 设置为true,所以Dialog样式的Activity默认情况下点击外部会关闭,如果我们调用setFinishOnTouchOutside(false)或者在styles文件中设置了 <item name="android:windowCloseOnTouchOutside">false</item> 那么最终都会将mCloseOnTouchOutside 变量置为false,点击Activity外部也就不会关闭了。

综上分析,Window中shouldCloseOnTouch大多数情况下是返回false的,从而Activity中onTouchEvent大多说情况下也是返回false,除非我们进行了特殊设置。这也就是Activity中onTouchEvent注释是The default implementation always returns false而不是The default implementation returns false,就多了一个always。

好了,到此为止关于安卓事件传递机制最重要的部分都已经讲解完毕,最最核心的还是要掌握View以及ViewGroup的部分,至于Activity的传递大体了解一下流程就可以了。

下一篇我们一起探究一下Activity中setContentView方法究竟做了什么事情。反过来你能更好理解本篇中的某些点。

Android事件传递机制详解及最新源码分析——Activity篇的更多相关文章

  1. Android事件传递机制详解及最新源码分析——ViewGroup篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...

  2. Android事件传递机制详解及最新源码分析——View篇

    摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于安卓事件传递机制相信绝大部分开发者都听说过或者了解过,也是面试中最常问的问题之一.但是真正能从源码角度理解具体事件传递流程的相信并不多, ...

  3. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  4. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

  5. Android Touch事件传递机制详解 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  6. 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING

    <Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th  2014 Email:skyseraph00@163.com 更多精彩请直接 ...

  7. Android事件分发机制详解

    事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...

  8. Android Touch事件传递机制详解 上

    最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘 ...

  9. Android事件分发机制详解(1)----探究View的事件分发

    探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...

随机推荐

  1. ReentrantLock实现原理及源码分析

    ReentrantLock是Java并发包中提供的一个可重入的互斥锁.ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性.只不过相比原生的Sync ...

  2. 基于FPGA的VGA显示静态图片

    终于熬到暑假了,记过三四周的突击带考试,终于为我的大二画上了一个完整的句号,接下来终于可以静心去做自己想做的事情了,前一阵子报了一个线上培训班,学学Sobel边缘检测,之前一直在学习图像处理,但是因为 ...

  3. ES6数组扩展

    前面的话 数组是一种基础的JS对象,随着时间推进,JS中的其他部分一直在演进,而直到ES5标准才为数组对象引入一些新方法来简化使用.ES6标准继续改进数组,添加了很多新功能.本文将详细介绍ES6数组扩 ...

  4. Promise和异步编程

    前面的话 JS有很多强大的功能,其中一个是它可以轻松地搞定异步编程.作为一门为Web而生的语言,它从一开始就需要能够响应异步的用户交互,如点击和按键操作等.Node.js用回调函数代替了事件,使异步编 ...

  5. Swift 细节

    1.swift ?和 !的区别 1.1 Swift语言使用var定义变量,但和别的语言不同,Swift里不会自动给变量赋初始值,也就是说变量不会有默认值,所以要求使用变量之前必须要对其初始化.如果在使 ...

  6. 记录easyui一些用法

    自己备注,省的之后忘记.用到一个写一个,不断添加 1.form里的一些控件如textbox.combobox等添加额外的一些事件,如鼠标事件(mouseover.click等),键盘事件(keydow ...

  7. ABP:在多语句事务内不允许使用 CREATE DATABASE 语句

    一.问题 使用ef codefirst开发,无法创建数据库的问题,如下提示 Server Error in '/' Application. 在多语句事务内不允许使用 CREATE DATABASE ...

  8. macvlan 网络结构分析 - 每天5分钟玩转 Docker 容器技术(56)

    上一节我们创建了 macvlan 并部署了容器,本节详细分析 macvlan 底层网络结构. macvlan 网络结构分析 macvlan 不依赖 Linux bridge,brctl show 可以 ...

  9. Adaboost、RandomFrest、GBRT的区别

    Adaboost.RandomFrest.GBRT都是基于决策树的组合算法 Adaboost是通过迭代地学习每一个基分类器,每次迭代中,把上一次错分类的数据权值增大,正确分类的数据权值减小,然后将基分 ...

  10. 前端布局之Flex语法

    前端布局一直是CSS的一个重点应用,然而基于盒子模型的传统布局方案,依赖display + position + float 属性,对于某些特殊的布局非常不方便,比如:垂直居中就不容易实现.针对这一情 ...