其实 Android 事件分发机制在早几年一直都困扰着我,那时候处理事件分发的自定义 View 脑子都是一片白,老感觉处理不好。后来自己看了 android 源码,也阅读了很多大牛的文章才算彻底明白,总之掌握 Android 事件分发机制是必不可少的,而 Android 事件分发机制绝对不是三言两语就能说得清的。

而今天由于我们自定义 View 进阶的需要,自己也是筹备了很久。目前虽然网上相关的文章也不少,很多也写得非常详细,但是多数文章只是讲了讲理论,然后配合 Log 打印一下结果而已。而我准备不仅带着大家从源码的角度进行分析,还需要理论结合实践写几个关于这方面的效果,这样相信我们会有更深的理解。阅读源码讲究由浅入深,循序渐进,我们就不像其他文章一样搞混合了,先讲 View 的 Touch 事件分发,然后再讲 ViewGroup 的事件分发,最后再写个几次效果。我们一贯的套路都是理论结合实践,由浅入深

先来看几个效果,如前几次我们写自定义评分控件的 RatingBar 复写了 onTouchEvent(),这里只是举个例子:

public class RatingBar extends View {public RatingBar(Context context) {super(context);}public RatingBar(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.d("TAG", "onTouchEvent execute -> " + event.getAction());return super.onTouchEvent(event);}
}
复制代码

如果想要给这个控件注册一个点击事件,只需要调用:

mRatingBar.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d("TAG","onClick execute");}
});
复制代码

如果想给这个按钮再添加一个touch事件,只需要调用:

mRatingBar.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.d("TAG", "onTouch execute -> " + event.getAction());return false;}
});
复制代码

上面的代码如果运行起来,哪一个会先执行呢? 如果是靠猜,那么我们来试一下就知道了,运行程序点击,打印结果如下:

可以看到,onTouch 是优先于优先于 onTouchEvent 优先于 onClick 执行的,并且 onTouch 和 onTouchEvent 执行了两次,一次是 ACTION_DOWN ,一次是 ACTION_UP (你还可能会有多次 ACTION_MOVE 的执行,如果你在上面触摸)。因此事件传递的顺序是先经过 onTouch ,然后经过 onTouchEvent ,再传递到 onClick 。

如果留心观察你会发现 setOnTouchListener 是有返回值的,如果返回 ture ,再次运行一下会怎样?

我们发现,onTouchEvent 和 onClick 方法不再执行了!为什么会这样呢?你可以先理解成 onTouch 方法返回 true 就认为这个事件被 onTouch 消费掉了,因而不会再继续 onTouchEvent 和 onClick 。到目前位置如果你清楚了,那么面试的时候基本靠背,那么自己写效果的时候基本靠蒙。我们肯定不能局限于这个装态,接下了我们就带着疑问从源码的角度分析一下,为什么会出现上述情况?

首先我们需要知道,你点击或者或者触摸任何一个 View 都会调用 View 的 dispatchTouchEvent() 方法,我们就从这里开始分析源码:

    public boolean dispatchTouchEvent(MotionEvent event) {// 省略部分代码 ...boolean result = false;// 省略部分代码 ...if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}if (!result && onTouchEvent(event)) {result = true;}}// 返回 resultreturn result;}
复制代码

省略掉部分代码之后,这个方法就变得非常的简洁了,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果li != null,mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) 这三个条件都为真,result 就是 true,否则就去执行 onTouchEvent(event) 方法并返回。   那么 ListenerInfo 到底是什么?我们可以看下源码,这其实就是有关 View 所有事件的一个集合类,如 OnFocusChangeListener , OnScrollChangeListener , OnClickListener 、、、

    static class ListenerInfo {/*** Listener used to dispatch focus change events.* This field should be made private, so it is hidden from the SDK.* {@hide}*/protected OnFocusChangeListener mOnFocusChangeListener;/*** Listeners for layout change events.*/private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;protected OnScrollChangeListener mOnScrollChangeListener;/*** Listeners for attach events.*/private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;/*** Listener used to dispatch click events.* This field should be made private, so it is hidden from the SDK.* {@hide}*/public OnClickListener mOnClickListener;/*** Listener used to dispatch long click events.* This field should be made private, so it is hidden from the SDK.* {@hide}*/protected OnLongClickListener mOnLongClickListener;/*** Listener used to dispatch context click events. This field should be made private, so it* is hidden from the SDK.* {@hide}*/protected OnContextClickListener mOnContextClickListener;/*** Listener used to build the context menu.* This field should be made private, so it is hidden from the SDK.* {@hide}*/protected OnCreateContextMenuListener mOnCreateContextMenuListener;private OnKeyListener mOnKeyListener;private OnTouchListener mOnTouchListener;private OnHoverListener mOnHoverListener;private OnGenericMotionListener mOnGenericMotionListener;private OnDragListener mOnDragListener;private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;}
复制代码

先看一下条件 li.mOnTouchListener 这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:

    /*** Register a callback to be invoked when a touch event is sent to this view.* @param l the touch listener to attach to this view*/public void setOnTouchListener(OnTouchListener l) {getListenerInfo().mOnTouchListener = l;}ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;}
复制代码

第二个条件 mOnTouchListener 正是在 setOnTouchListener 方法里赋值的,也就是说只要我们给控件注册了 touch 事件,mListenerInfo 和 mListenerInfo.mOnTouchListener 就一定被赋值了。

第三个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,默认都是enable的,因此这个条件恒定为 true 。

第四个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册 touch 事件时的 onTouch 方法。也就是说如果我们在 onTouch 方法里返回true,就会让这三个条件全部成立,从而 result 是 true , 那么 onTouchEvent 就不会被执行 。如果我们在 onTouch 方法里返回 false,就会去执行 onTouchEvent() 方法。

现在我们可以结合前面的例子来分析一下了,首先在 dispatchTouchEvent 中最先执行的就是 onTouch 方法,因此 onTouch 肯定是要优先于 onTouchEvent 方法,也是印证了刚刚的打印结果。而如果在 onTouch 方法里返回了 true,不会再执行 onTouchEvent 。但是到目前位置我们还没有看到 onClick 执行,但是我们可以猜到,onClick的调用肯定是在onTouchEvent(event)方法中的!那我们马上来看下onTouchEvent的源码,如下所示:

    public boolean onTouchEvent(MotionEvent event) {// 省略部分代码if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP:boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {performClick();}mIgnoreNextUpEvent = false;break;// 省略部分代码}return true;}return false;}public boolean performClick() {final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);return result;}
复制代码

相较于刚才的 dispatchTouchEvent 方法,onTouchEvent 方法复杂了很多,不过没关系,我们只挑重点看就可以了。switch 中如果当前的事件是抬起手指,则会进入到 MotionEvent.ACTION_UP 这个 case 当中。在经过种种判断之后,会执行到 performClick() 方法,可以看到,只要 mListenerInfo.mOnClickListener 不是 null,就会去调用它的 onClick 方法,那 mListenerInfo.mOnClickListener 又是在哪里赋值的呢?我们大概能猜到肯定在 setOnclickLstener 方法中:

    public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener = l;}ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;}
复制代码

View 的 Touch 事件分发我们就讲到这里了,下一节我将带着大家一起了解 ViewGroup 的事件分发和事件拦截,在源码的基础上写几个效果,我想应该可以说就堪称完美了。

所有分享大纲:Android进阶之旅 - 自定义View篇

视频讲解地址:pan.baidu.com/s/1hr6ql72

源码阅读分析 View的Touch事件分发相关推荐

  1. Android6.0源码解读之ViewGroup点击事件分发机制

    本篇博文是Android点击事件分发机制系列博文的第三篇,主要是从解读ViewGroup类的源码入手,根据源码理清ViewGroup点击事件分发原理,明白ViewGroup和View点击事件分发的关系 ...

  2. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

  3. Vuex源码阅读分析

    Vuex源码阅读分析 Vuex是专为Vue开发的统一状态管理工具.当我们的项目不是很复杂时,一些交互可以通过全局事件总线解决,但是这种观察者模式有些弊端,开发时可能没什么感觉,但是当项目变得复杂,维护 ...

  4. NJ4X源码阅读分析笔记系列(一)——项目整体分析

    NJ4X源码阅读分析笔记系列(一)--项目整体分析 NJ4X是什么 参见NJ4X的官网:http://www.nj4x.com/ Java and .Net interfaces to support ...

  5. NJ4X源码阅读分析笔记系列(三)—— nj4x-ts深入分析

    NJ4X源码阅读分析笔记系列(三)-- nj4x-ts深入分析 一.系统的工作流程图(模块级) 其工作流程如下(以行情获取为例): 应用端向Application Server发起连接 应用服务器调用 ...

  6. View的Touch事件分发(一.初步了解)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 一次完整的Touch事件序列为: ACTION_DOWN -> AC ...

  7. Kubernetes 1.12.0 Kube-controller-manager之replicaset-controller源码阅读分析

    前言 Kube-controller-manager组件最终启动了很多controller,本文将对其中的replicaset-controller的源码进行阅读分析. 启动replicaset-co ...

  8. Scrapy源码阅读分析_3_核心组件

    From:https://blog.csdn.net/weixin_37947156/article/details/74481758 这篇这要是关于核心组件,讲解这些核心组件初始化都做了哪些工作.包 ...

  9. Scrapy源码阅读分析_2_启动流程

    From:https://blog.csdn.net/weixin_37947156/article/details/74436333 使用 PyCharm 打开下载好的 Scrapy 源码(gith ...

最新文章

  1. 服务器技术综述(二)
  2. java 数组越界异常_数组越界异常 求解决!!!
  3. mxGraph破解说明
  4. 【MPI编程】任意节点数的蝶形求和(高性能计算)
  5. git学习相关的博客地址
  6. java8.0 platform图_Java Platform SE binary语言-Java编程32位/64位版(jdk-jeb)下载V8.0.2510.8官方安装版-西西软件下载...
  7. (10)Vivado 异步时钟约束
  8. ADO.NET 完整修改、删除、防字符串攻击
  9. FluorineFx对于现有站点的配置
  10. 自制操作系统(五) 保护模式寻址原理、字符鼠标指针显示
  11. 开源ext2read代码走读之-如何读取MBR分区的内容
  12. 压垮硬盘的最后一次备份
  13. The Performance of µ-Kernel-Based Systems
  14. 为大家推荐一个全新的跨平台app软件开发工具——Lae软件开发平台
  15. xiaomi 小米6刷ubuntu touch
  16. Android开发详解之App升级程序一点通
  17. 学习笔记——LED跑马灯
  18. 制作linux包 u盘安装
  19. syslog与rsyslog
  20. cin.tie与sync_with_stdio加速I/O

热门文章

  1. python图片转字符画
  2. GSM手机SMS编码解码
  3. 在Visual Studio 2005里,用ActiveSync来同步模拟器(Windows Mobile 5.0 )
  4. Mob统计分析数据模型理解
  5. 中国电信在青岛率先商用NB-IoT 投入30亿推进信息化建设
  6. 教你怎么快速配置 React
  7. U盘中的autorun.inf
  8. 查看每个表空间的使用率
  9. Linux系统下MBR分区表的备份与恢复
  10. C++ 之虚函数的实现原理