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

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

从Demo示例说起

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

  1. public class MyButton extends Button {
  2.  
  3. private final String TAG = "WL";
  4.  
  5. public MyButton(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7. }
  8.  
  9. @Override
  10. public boolean dispatchTouchEvent(MotionEvent ev) {
  11. //
  12. Log.i(TAG, "MyButton_dispatchTouchEvent_Action:"+ev.getAction());
  13. return super.dispatchTouchEvent(ev);
  14. }
  15.  
  16. @Override
  17. public boolean onTouchEvent(MotionEvent event) {
  18. //
  19. Log.i(TAG, "MyButton_onTouchEvent_Action:"+event.getAction());
  20. return super.onTouchEvent(event);
  21. }
  22. }

自定义LinearLayout:

  1. public class MyLinearLayout extends LinearLayout {
  2.  
  3. private final String TAG = "WL";
  4.  
  5. public MyLinearLayout(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7. //
  8. }
  9.  
  10. @Override
  11. public boolean dispatchTouchEvent(MotionEvent ev) {
  12. //
  13. Log.i(TAG, "MyLinearLayout_dispatchTouchEvent_Action:"+ev.getAction());
  14. return super.dispatchTouchEvent(ev);
  15. }
  16.  
  17. @Override
  18. public boolean onTouchEvent(MotionEvent event) {
  19. //
  20. Log.i(TAG, "MyLinearLayout_onTouchEvent_Action:"+event.getAction());
  21. return super.onTouchEvent(event);
  22. }
  23. }

布局文件:

  1. <com.wl.activitydispatchtouchevent.MyLinearLayout
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:background="#0099cc"
  7. android:id="@+id/mylinearlayout"
  8. android:gravity="center"
  9. tools:context="com.wl.activitydispatchtouchevent.MainActivity" >
  10.  
  11. <com.wl.activitydispatchtouchevent.MyButton
  12. android:id="@+id/mybutton"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:textSize="20sp"
  16. android:text="WL_Button" />
  17.  
  18. </com.wl.activitydispatchtouchevent.MyLinearLayout>

Activity中代码:

  1. public class MainActivity extends Activity implements OnClickListener,
  2. OnTouchListener {
  3.  
  4. private final String TAG = "WL";
  5.  
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_fullscreen);
  10. //
  11. findViewById(R.id.mybutton).setOnClickListener(this);
  12. findViewById(R.id.mybutton).setOnTouchListener(this);
  13. //
  14. findViewById(R.id.mylinearlayout).setOnClickListener(this);
  15. findViewById(R.id.mylinearlayout).setOnTouchListener(this);
  16. }
  17.  
  18. @Override
  19. public boolean onTouch(View v, MotionEvent event) {
  20. //
  21. Log.i(TAG, "onTouch___v:" + v + "___action:" + event.getAction());
  22. return false;
  23. }
  24.  
  25. @Override
  26. public void onClick(View v) {
  27. //
  28. Log.i(TAG, "onClick___v:" + v);
  29. }
  30.  
  31. @Override
  32. public boolean dispatchTouchEvent(MotionEvent ev) {
  33. Log.i(TAG, "MainActivity__dispatchTouchEvent__action:" + ev.getAction());
  34. return super.dispatchTouchEvent(ev);
  35. }
  36.  
  37. @Override
  38. public boolean onTouchEvent(MotionEvent event) {
  39. Log.i(TAG, "MainActivity___onTouchEvent___action=" + event.getAction());
  40. return super.onTouchEvent(event);
  41. }
  42. }

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

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

  1. MainActivity__dispatchTouchEvent__action:0
  2. MyLinearLayout_dispatchTouchEvent_Action:0
  3. MyButton_dispatchTouchEvent_Action:0
  4. onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:0
  5. MyButton_onTouchEvent_Action:0
  6. MainActivity__dispatchTouchEvent__action:1
  7. MyLinearLayout_dispatchTouchEvent_Action:1
  8. MyButton_dispatchTouchEvent_Action:1
  9. onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:1
  10. MyButton_onTouchEvent_Action:1
  11. onClick___v:com.wl.activitydispatchtouchevent.MyButton

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

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

Activity中dispatchTouchEvent方法源码如下:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  3. onUserInteraction();
  4. }
  5. if (getWindow().superDispatchTouchEvent(ev)) {
  6. return true;
  7. }
  8. return onTouchEvent(ev);
  9. }

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

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

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

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

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

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

  1. @Override
  2. public boolean superDispatchTouchEvent(MotionEvent event) {
  3. return mDecor.superDispatchTouchEvent(event);
  4. }

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

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

  1. private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
  2.  
  3. ..........
  4. public boolean superDispatchTouchEvent(MotionEvent event) {
  5. return super.dispatchTouchEvent(event);
  6. }
  7.  
  8. ..........
  9. }

我勒个去,搞半天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方法,源码如下;

  1. /**
  2. * Called when a touch screen event was not handled by any of the views
  3. * under it. This is most useful to process touch events that happen
  4. * outside of your window bounds, where there is no view to receive it.
  5. *
  6. * @param event The touch screen event being processed.
  7. *
  8. * @return Return true if you have consumed the event, false if you haven't.
  9. * The default implementation always returns false.
  10. */
  11. public boolean onTouchEvent(MotionEvent event) {
  12. if (mWindow.shouldCloseOnTouch(this, event)) {
  13. finish();
  14. return true;
  15. }
  16.  
  17. return false;
  18. }

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

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

  1. public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
  2. if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
  3. && isOutOfBounds(context, event) && peekDecorView() != null) {
  4. return true;
  5. }
  6. return false;
  7. }

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

  1. public final View peekDecorView() {
  2. return mDecor;
  3. }

很简单,就是返回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. Verilog HDL程序设计——基本要素

    Verilog基本上熟悉了,继续整理一下Verilog的学习笔记吧.前面记载了Verilog的结构,写Verilog的结构有了,但是该怎么写呢?在写之前就得了解一下Verilog的一些基本要素了,也就 ...

  2. javascript编程解决黑白卡片问题

    问题描述: 时间限制:1秒 空间限制:32768K 牛牛有n张卡片排成一个序列.每张卡片一面是黑色的,另一面是白色的.初始状态的时候有些卡片是黑色朝上,有些卡片是白色朝上.牛牛现在想要把一些卡片翻过来 ...

  3. 使用Mapper专用的MyBatis Generator插件

    使用Maven执行MBG 这里有一个完整的例子,Mybatis-Spring,下面讲解的内容出自这个例子. 使用Maven插件的一个好处是可以将Maven中的属性使用${property}形式在gen ...

  4. 为什么是Spring Boot

    原文:https://dzone.com/articles/why-springboot 作者:Siva Prasad Reddy Katamreddy 译者:Oopsguy 本文介绍将各种Sprin ...

  5. 初学Python之 安装包的抉择~~

    上面的都是windows系统平台的安装包,哇,有没有后宫三千,不知道"临幸"哪一个的感觉~.~ 看了下面的你就明白啦. 毫无疑问,x86适合32位操作系统:x86-64适合64位操 ...

  6. Fail2防止sshd暴力破解

    简介: fail2ban是一款实用软件,可以监视你的系统日志,然后匹配日志的错误信息(正则式匹配)执行相应的屏蔽动作.支持大量服务.如sshd,apache,qmail,proftpd,sasl等等 ...

  7. kafka在windows下的安装和配置

    博主最近在学习有关kafka的配置安装以及在spring的集成使用.但网上关于kafka的配置参考资料基本都是于linux下的配置,于是博主在整理了相关windows下kafka的配置记录在博客里.由 ...

  8. 案例:数据库open时报错ORA-1172,ORA-1151 处理

    环境:OEL 5.7 + Oracle 10.2.0.5 Clusterware + Oracle 10.2.0.5 RAC 故障:数据库open时报错ORA-1172,ORA-1151 1.故障详细 ...

  9. 【有意思的BUG】分享按钮 分享功能

    [分享按钮]是一个常见的功能,你可以把看到的有意思的东西分享到自己的BLOG.朋友圈之类的地方. 但是,分享出去的文本(也可以包含图片)在每个目标网站上面的格式并不是统一的,所以就存在了美感的三六九等 ...

  10. ORACLE索引监控的简单使用

    --ORACLE索引监控的简单使用-------------------------2013/11/20 说明:     应用程序在开发时,可能会建立众多索引,但是这些索引的使用到底怎么样,是否有些索 ...