一、点击事件介绍

在Android 的点击事件中我们涉及到ViewGroup类的三个方法:

  @Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(TAG, "dispatchTouchEvent: ");return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(TAG, "onTouchEvent: ");return super.onTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(TAG, "onInterceptTouchEvent: ");return super.onInterceptTouchEvent(ev);}

其中view与activity只有dispatchTouchEvent(),onTouchEvent()这两个方法,而onInterceptTouchEvent()则是viewGroup独有的方法,我们平常说的点击事件传递也主要复写这三个方法,先来了解一下这几个方法:

(1)dispatchTouchEvent

dispatchTouchEvent即点击事件的分发,这个有点类网络的路由器,是其他点击事件的主要拦路虎,可以说这个方法决定了事件能不能接着向下传递,一般我们不会复写这个方法,返回默认值即可,如果返回true或者false,会发现事件都不会往下传递,那么返回false与true又有什么区别呢?返回false表明事件到这里不会再接着往下传递,而是返回给父viewGroup的OnTouchEvent去处理该事件,而返回 true表明事件既不会往下传递,也不会返回给父VIewGroup进行处理,而是自己把事件给消化掉,什么也不做

(2)onInterceptTouchEvent

onInterceptTouchEvent即事件的拦截器,问题来了,为什么有了一个事件分发,怎么还整了一个事件的拦截?岂不是多余的吗?其实仔细想一下就会发现,他并不多余,在上一层的dispatchTouchEvent返回默认值,事件就会传递到这个方法,有点像局域网内的内部IP,起到导流的作用。一般在处理点击事件冲突的时候,主要复写这个方法来判断是否需要拦截事件,如果返回true说明事件被该ViewGroup拦截了,事件也就不会传递到下一级的ViewGroup或者是View,直接由该ViewGroup处理;如果返回false,表明该ViewGroup不对事件做拦截处理,直接让事件流向下一级

(3)onTouchEvent

onTouchEvent 即事件的逻辑处理,在onInterceptTouchEvent方法中,如果反回true,事件就会流到这个方法。在onTouchEvent方法中如果返回true,那么表示消费了该事件,返回false则表示不消费该事件并将事件往上传递,交给其父viewGroup处理

另外,如果我们的view实现了View.OnTouchListener接口,并重写了onTouch方法,那么这个方法会比onTouchEvent方法优先调用,并且如果onTouch方法返回true的话,onTouchEvent将不会被调用

二、案例讲解

其实上面的几个方法,大家也或多或少有点了解了,接下来我会根据实例来讲解Android的点击事件的具体分发流程
先创建两个个自定义的CustomRel和CustomRel2,复写以上三个方法:
public class CustomRel extends RelativeLayout{private static final String TAG = "CustomRel";public CustomRel(Context context) {super(context);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(TAG, "dispatchTouchEvent: ");return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(TAG, "onTouchEvent: ");return super.onTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(TAG, "onInterceptTouchEvent: ");return super.onInterceptTouchEvent(ev);}
}

其布局如下,CustomRel嵌套着CustomRel2

我们就一个一个的来验证,注意:以下均用控制变量法来验证,即只修改一处,其他均为默认值

(1)dispatchTouchEvent

先修改CustomRel2 的dispatchTouchEvent方法,返回默认值,打印结果如下

09-19 16:20:14.915 2255-2255/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:20:14.915 2255-2255/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:20:14.916 2255-2255/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 16:20:14.916 2255-2255/com.ujs.tounchtest I/CustomRel2: onInterceptTouchEvent:
09-19 16:20:14.916 2255-2255/com.ujs.tounchtest I/CustomRel2: onTouchEvent:
09-19 16:20:14.917 2255-2255/com.ujs.tounchtest I/CustomRel: onTouchEvent: 

我们可以看到,事件传递到CustomRel2 的onTouchEvent方法,由于我们在这个方法里面什么也不做,事件又重新交给一层的ViewGroup的onTouchEvent方法处理,这就是传说中的U型图

那么返回false会如何呢?打印如下:
09-19 16:27:23.613 15488-15488/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:27:23.613 15488-15488/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:27:23.613 15488-15488/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 16:27:23.613 15488-15488/com.ujs.tounchtest I/CustomRel: onTouchEvent: 

可以看到,事件传递到CustomRel2 的dispatchTouchEvent方法时候,由于返回了false,CustomRel2 的其他两个方法均不会调用,而是直接将事件返回给上一层ViewGroup
onTouchEvent方法进行处理

返回true呢?
09-19 16:32:38.974 24932-24932/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:32:38.974 24932-24932/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:32:38.974 24932-24932/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 16:32:38.995 24932-24932/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:32:38.996 24932-24932/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:32:38.996 24932-24932/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent: 

发现事件到了CustomRel2 的dispatchTouchEvent方法后就被消耗掉了,既不调用之后的两个方法也不会返回给上一层ViewGroup进行处理

(2)onInterceptTouchEvent

通过上面的讲解,我们知道onInterceptTouchEvent是用来对事件的拦截的,那么我们就来修改CustomRel的onInterceptTouchEvent方法,反回默认值与最刚开始的打印结果一致,直接修改返回false来看看:

09-19 16:43:01.119 13004-13004/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:43:01.119 13004-13004/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:43:01.119 13004-13004/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 16:43:01.119 13004-13004/com.ujs.tounchtest I/CustomRel2: onInterceptTouchEvent:
09-19 16:43:01.119 13004-13004/com.ujs.tounchtest I/CustomRel2: onTouchEvent:
09-19 16:43:01.119 13004-13004/com.ujs.tounchtest I/CustomRel: onTouchEvent: 

结果与直接返回默认值一致,呈现U型传递

返回true试试看:
09-19 16:44:47.341 15717-15717/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:44:47.342 15717-15717/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:44:47.342 15717-15717/com.ujs.tounchtest I/CustomRel: onTouchEvent: 

我们发现,事件直接交给了CustomRel的onTouchEvent方法处理,没有向下传递,即事件被拦截掉了

(3)onTouchEvent

先重写两个类的onTouchEvent方法,获取点击事件的属性:

    @Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:Log.i(TAG, "onTouchEvent: down ");break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onTouchEvent: move");break;case MotionEvent.ACTION_UP:Log.i(TAG, "onTouchEvent: up ");break;}return super.onTouchEvent(event);}

这里也有一个返回值,先返回默认的

跟预期一样,结果如下:
09-19 16:59:22.903 27001-27001/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 16:59:22.903 27001-27001/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 16:59:22.903 27001-27001/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 16:59:22.903 27001-27001/com.ujs.tounchtest I/CustomRel2: onInterceptTouchEvent:
09-19 16:59:22.903 27001-27001/com.ujs.tounchtest I/CustomRel2: onTouchEvent: down
09-19 16:59:22.903 27001-27001/com.ujs.tounchtest I/CustomRel: onTouchEvent: down 

我们将CustomRel2 的onTouchEvent方法返回true看看

09-19 17:04:19.887 5766-5766/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 17:04:19.887 5766-5766/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 17:04:19.887 5766-5766/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 17:04:19.887 5766-5766/com.ujs.tounchtest I/CustomRel2: onInterceptTouchEvent:
09-19 17:04:19.887 5766-5766/com.ujs.tounchtest I/CustomRel2: onTouchEvent: down
09-19 17:04:19.981 5766-5766/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 17:04:19.981 5766-5766/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 17:04:19.981 5766-5766/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 17:04:19.982 5766-5766/com.ujs.tounchtest I/CustomRel2: onTouchEvent: up 

我们可以看到CustomRel的onTouchEvent方法并没有获得事件,CustomRel2直接将事件消耗掉了
而返回false与返回默认值一样,均会将事件回传给上一层viewGroup的onTouchEvent方法

09-19 17:08:46.785 10070-10070/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 17:08:46.785 10070-10070/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 17:08:46.785 10070-10070/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 17:08:46.785 10070-10070/com.ujs.tounchtest I/CustomRel2: onInterceptTouchEvent:
09-19 17:08:46.786 10070-10070/com.ujs.tounchtest I/CustomRel2: onTouchEvent: down
09-19 17:08:46.786 10070-10070/com.ujs.tounchtest I/CustomRel: onTouchEvent: down 

以上是针对子ViewGroup来说的,那如果父ViewGroup的onTouchEvent返回true呢?会把事件消耗掉吗?将CustomRel的onTouchEvent方法返回值修改为true,我们发现第一次事件传给子层后,CustomRel就直接把事件给消耗掉了,也就是说,当onTouchEvent反回true的时候,之后的事件默认就交给该ViewGroup的来处理,不再将事件向下传递

09-19 17:19:04.110 20055-20055/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 17:19:04.111 20055-20055/com.ujs.tounchtest I/CustomRel: onInterceptTouchEvent:
09-19 17:19:04.111 20055-20055/com.ujs.tounchtest I/CustomRel2: dispatchTouchEvent:
09-19 17:19:04.111 20055-20055/com.ujs.tounchtest I/CustomRel2: onInterceptTouchEvent:
09-19 17:19:04.111 20055-20055/com.ujs.tounchtest I/CustomRel2: onTouchEvent: down
09-19 17:19:04.112 20055-20055/com.ujs.tounchtest I/CustomRel: onTouchEvent: down
09-19 17:19:04.160 20055-20055/com.ujs.tounchtest I/CustomRel: dispatchTouchEvent:
09-19 17:19:04.160 20055-20055/com.ujs.tounchtest I/CustomRel: onTouchEvent: move

以上建议大家自行跑一遍就能体会了

三、源码分析

我们不仅要知其然,还要之气所以然,现在就参照源码,讲讲点击事件的分发为什么会是这样的。
首先我们要知道在Android的点击事件中主要分为两种:
1、按键的点击事件
2、屏幕的触摸事件

按键的事件主要由Wms来完成分发的 ,我们在实际中一般只考虑屏幕的触摸事件,在处理view的事件时有以下几点需要注意的:

1、屏幕触摸事件是直接分发给应用程序
2、子视图优先于父视图处理事件,即只有子视图消耗该事件了,父视图才有机会处理
3、在处理触摸事件时,需要根据触摸坐标来计算应该将事件分发给哪一个view/ViewGroup
我们先从DecorView入手,在该类的dispatchTouchEvent方法中有如下代码:
   @Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {final Window.Callback cb = mWindow.getCallback();return cb != null && !mWindow.isDestroyed() && mFeatureId < 0? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);}

这里先获取mWindow的callbak对象,如果没有就直接调用 基类的dispatchToucnEvent()方法
在ViewGroup中采用递归的方式分发事件:
1、触摸事件首先会把消息派发给view树中的最后一个子视图。如果该View没有消耗该事件,才会递归派发给父视图
在处理Down 消息的时候,其作用是判断点击事件是落在该ViewGroup的哪一个视图中

再来看看ViewGroup的dispatchTouchEvent方法
 (1)当MotionEvent的消息为Down的时候 ,先判断自身是否被禁止获取Touch消息,如果没有禁止,并且回调函数 onInterceptTouchEvent()中没有消耗该消息,就可以接着往下传递给子视图

     // Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}

然后开始根据坐标寻找子视图,主要作用是将布局坐标转化为视图坐标,获取到子view,在判断该子view 是否是ViewGroup,如果是就接着调用dispatchTouchEvent方法,否则递归结束

    final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);// Find a child that can receive the event.// Scan children from front to back.final ArrayList<View> preorderedList = buildTouchDispatchChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);...............}}

(2)当MotionEvent消息为UP或者cancel的时候,就清除 mGroupFlags 的FLAG_DISALLOW_INTERCEPT标志,即允许该ViewGroup截获消息。比如当用户释放手指,再次点击的时候,该ViewGroup可以再次截获消息,而当按下还没有释放期间,是不允许截获消息的

      // Update list of touch targets for pointer up or cancel, if needed.if (canceled|| actionMasked == MotionEvent.ACTION_UP|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {resetTouchState();}/*** Resets all touch state in preparation for a new cycle.*/private void resetTouchState() {clearTouchTargets();resetCancelNextUpFlag(this);mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;mNestedScrollAxes = SCROLL_AXIS_NONE;}

2、在上面viewGroup 把消息传递到了view,先看view的dispatchTouchEvent这个方法,里面有下面几行代码

    public boolean dispatchTouchEvent(MotionEvent event) {
...............if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//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;}}
.........................
}

可以看到,当onFilterTouchEventForSecurity方法返回true的时候才会调用OnTouch 跟onTouchAEvent 方法,onFilterTouchEventForSecurity代码如下

    /*** Filter the touch event to apply security policies.** @param event The motion event to be filtered.* @return True if the event should be dispatched, false if the event should be dropped.** @see #getFilterTouchesWhenObscured*/public boolean onFilterTouchEventForSecurity(MotionEvent event) {//noinspection RedundantIfStatementif ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {// Window is obscured, drop this touch.return false;}return true;}

通过注释我们可以很清楚的看到,如果onFilterTouchEventForSecurity方法返回true,说明该消息应该分发到当前的view,否则不分发。我们注意到这一行注释

// Window is obscured, drop this touch.

如果当前Window 为模糊状态,那么他内部的所有view都应该为模糊效果,此时消息就不能分发到当前的view
回到dispatchTouchEvent方法中,判断当前mOnTouchListener是否为空,不为空就调用onTouch方法(注:OnTouch方法是View.OnTouchListener的抽象方法),如果onTouch返回true,那么就直接跳过onTouchEvent方法,否则调用onTouchEvent方法进行处理。这也就是为什么onTouch方法会优先于onTouchEvent方法的原因

三、总结

至此我们就理顺了事件分发的三个方法的调用顺序以及返回值的作用,我们可以通过onInterceptTouchEvent的返回值来告诉系统我们是否需要拦截事件,通过onTouchEvent返回值来确定我们是否消耗点击事件,并对事件进行处理

浅谈android的事件分发机制相关推荐

  1. Android之事件分发机制

    本文主要包括以下内容 view的事件分发 viewGroup的事件分发 首先来看两张图 在执行touch事件时 首先执行dispatchTouchEvent方法,执行事件分发. 再执行onInterc ...

  2. Android 系统(218)---Android的事件分发机制以及滑动冲突的解决

    Android的事件分发机制以及滑动冲突的解决 声明:  本文主要涉及VIew的事件分发与滑动冲突的解决,关于View的事件分发流程的部分内容参考自:  Android事件分发机制详解:史上最全面.最 ...

  3. Android系统(120)-android的事件分发机制

    android的事件分发机制 android的事件分发机制 比如说,现在你所在的公司中有一项任务被派发下来了,项目经理把项目交给你的老大,你的老大老大手下有很多人,看了看觉得你做很合适,把这个任务交给 ...

  4. Android的事件分发机制

    前言 Android事件分发机制是Android开发者必须了解的基础 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将把 ...

  5. android SDK-25事件分发机制--源码正确解析

    android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...

  6. 一文读懂Android View事件分发机制

    Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...

  7. Android ViewGroup事件分发机制

    理~ 1.案例 首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout: [java] view plaincopy package com.example.zhy_event03 ...

  8. Android View 事件分发机制详解

    想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...

  9. 完全理解Android TouchEvent事件分发机制(一)

    本文能给你带来和解决一些你模糊的Touch事件概念及用法 1.掌握View及ViewGroup的TouchEvent事件分发机制 2.为解决View滑动冲突及点击事件消费提供支持 3.为你解决面试中的 ...

最新文章

  1. 领歌LeangooV6.5.3:支持评论编辑,看板动态跳转
  2. java hibernate configuration 获取_1 Hibernate Configuration 配置
  3. Android手机启动流程与TEE OS
  4. 【原创】RabbitMQ 之 Access Control(翻译)
  5. 055_Unicode字符官方标准六
  6. java 2d 绘图教程_Java标准教程:Java 2D绘图--第2章 从绘图开始
  7. AppVerifier的功能和原理
  8. 产品部门四大角色——PM/PD/UE/UI
  9. android 修复工具,牛学长安卓手机修复工具(Reiboot for Android)
  10. DiskGeniux无损分区
  11. windows10未安装画图工具解决办法
  12. 夜深人静写算法(二) - 动态规划
  13. 华为手机桌面有计算机的数字,华为手机桌面上信息图标上显示的数字角标如何关闭...
  14. 尝试使用Visual studio编写Android程序C++的跨平台开发Android
  15. linux内核5.8.1,Linus Torvalds宣布大规模更新Linux内核5.8
  16. 知识管理:营建学习型团队
  17. xsy3320 string
  18. HCM 初学 ( 二 ) - 信息类型
  19. MySQL性能调优必知:Performance Schema引擎的配置与使用
  20. C++编程(二):CURL错误码及含义

热门文章

  1. linux驱动导出文件属性,linux驱动入门——模块参数和导出符号
  2. maya怎么保持2u_【肘进健康】手肘超伸怎么练?
  3. C++知识点8——函数的返回值
  4. 记录一下从标定模型中读取参数
  5. get_sheet_of_light_result_object_model_3d算子说明
  6. oracle插入表为文件,将文本文件插入Oracle表中
  7. matlab jdbc mysql_Matlab连接MySQL(ODBC及JDBC驱动)
  8. Coding and Paper Letter(二十四)
  9. 《程序员代码面试指南》第二章 链表问题 构造链表和节点的实体
  10. Silverlight2 边学边练 之五 视频