Android事件传递机制详解及最新源码分析——Activity篇
版权声明:本文出自汪磊的博客,转载请务必注明出处。
在前两篇我们共同探讨了事件传递机制《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篇的更多相关文章
- Android事件传递机制详解及最新源码分析——ViewGroup篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...
- Android事件传递机制详解及最新源码分析——View篇
摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于安卓事件传递机制相信绝大部分开发者都听说过或者了解过,也是面试中最常问的问题之一.但是真正能从源码角度理解具体事件传递流程的相信并不多, ...
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象
前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...
- Android 的事件传递机制,详解
Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...
- Android Touch事件传递机制详解 下
尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...
- 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING
<Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th 2014 Email:skyseraph00@163.com 更多精彩请直接 ...
- Android事件分发机制详解
事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...
- Android Touch事件传递机制详解 上
最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘 ...
- Android事件分发机制详解(1)----探究View的事件分发
探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...
随机推荐
- Verilog HDL程序设计——基本要素
Verilog基本上熟悉了,继续整理一下Verilog的学习笔记吧.前面记载了Verilog的结构,写Verilog的结构有了,但是该怎么写呢?在写之前就得了解一下Verilog的一些基本要素了,也就 ...
- javascript编程解决黑白卡片问题
问题描述: 时间限制:1秒 空间限制:32768K 牛牛有n张卡片排成一个序列.每张卡片一面是黑色的,另一面是白色的.初始状态的时候有些卡片是黑色朝上,有些卡片是白色朝上.牛牛现在想要把一些卡片翻过来 ...
- 使用Mapper专用的MyBatis Generator插件
使用Maven执行MBG 这里有一个完整的例子,Mybatis-Spring,下面讲解的内容出自这个例子. 使用Maven插件的一个好处是可以将Maven中的属性使用${property}形式在gen ...
- 为什么是Spring Boot
原文:https://dzone.com/articles/why-springboot 作者:Siva Prasad Reddy Katamreddy 译者:Oopsguy 本文介绍将各种Sprin ...
- 初学Python之 安装包的抉择~~
上面的都是windows系统平台的安装包,哇,有没有后宫三千,不知道"临幸"哪一个的感觉~.~ 看了下面的你就明白啦. 毫无疑问,x86适合32位操作系统:x86-64适合64位操 ...
- Fail2防止sshd暴力破解
简介: fail2ban是一款实用软件,可以监视你的系统日志,然后匹配日志的错误信息(正则式匹配)执行相应的屏蔽动作.支持大量服务.如sshd,apache,qmail,proftpd,sasl等等 ...
- kafka在windows下的安装和配置
博主最近在学习有关kafka的配置安装以及在spring的集成使用.但网上关于kafka的配置参考资料基本都是于linux下的配置,于是博主在整理了相关windows下kafka的配置记录在博客里.由 ...
- 案例:数据库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.故障详细 ...
- 【有意思的BUG】分享按钮 分享功能
[分享按钮]是一个常见的功能,你可以把看到的有意思的东西分享到自己的BLOG.朋友圈之类的地方. 但是,分享出去的文本(也可以包含图片)在每个目标网站上面的格式并不是统一的,所以就存在了美感的三六九等 ...
- ORACLE索引监控的简单使用
--ORACLE索引监控的简单使用-------------------------2013/11/20 说明: 应用程序在开发时,可能会建立众多索引,但是这些索引的使用到底怎么样,是否有些索 ...