浅谈android的事件分发机制
一、点击事件介绍
在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
(2)onInterceptTouchEvent
(3)onTouchEvent
另外,如果我们的view实现了View.OnTouchListener接口,并重写了onTouch方法,那么这个方法会比onTouchEvent方法优先调用,并且如果onTouch方法返回true的话,onTouchEvent将不会被调用
二、案例讲解
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型图
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方法进行处理
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型传递
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
以上建议大家自行跑一遍就能体会了
三、源码分析
1、按键的点击事件2、屏幕的触摸事件
按键的事件主要由Wms来完成分发的 ,我们在实际中一般只考虑屏幕的触摸事件,在处理view的事件时有以下几点需要注意的:
1、屏幕触摸事件是直接分发给应用程序2、子视图优先于父视图处理事件,即只有子视图消耗该事件了,父视图才有机会处理3、在处理触摸事件时,需要根据触摸坐标来计算应该将事件分发给哪一个view/ViewGroup
@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方法的原因
三、总结
浅谈android的事件分发机制相关推荐
- Android之事件分发机制
本文主要包括以下内容 view的事件分发 viewGroup的事件分发 首先来看两张图 在执行touch事件时 首先执行dispatchTouchEvent方法,执行事件分发. 再执行onInterc ...
- Android 系统(218)---Android的事件分发机制以及滑动冲突的解决
Android的事件分发机制以及滑动冲突的解决 声明: 本文主要涉及VIew的事件分发与滑动冲突的解决,关于View的事件分发流程的部分内容参考自: Android事件分发机制详解:史上最全面.最 ...
- Android系统(120)-android的事件分发机制
android的事件分发机制 android的事件分发机制 比如说,现在你所在的公司中有一项任务被派发下来了,项目经理把项目交给你的老大,你的老大老大手下有很多人,看了看觉得你做很合适,把这个任务交给 ...
- Android的事件分发机制
前言 Android事件分发机制是Android开发者必须了解的基础 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将把 ...
- android SDK-25事件分发机制--源码正确解析
android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...
- 一文读懂Android View事件分发机制
Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...
- Android ViewGroup事件分发机制
理~ 1.案例 首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout: [java] view plaincopy package com.example.zhy_event03 ...
- Android View 事件分发机制详解
想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...
- 完全理解Android TouchEvent事件分发机制(一)
本文能给你带来和解决一些你模糊的Touch事件概念及用法 1.掌握View及ViewGroup的TouchEvent事件分发机制 2.为解决View滑动冲突及点击事件消费提供支持 3.为你解决面试中的 ...
最新文章
- 领歌LeangooV6.5.3:支持评论编辑,看板动态跳转
- java hibernate configuration 获取_1 Hibernate Configuration 配置
- Android手机启动流程与TEE OS
- 【原创】RabbitMQ 之 Access Control(翻译)
- 055_Unicode字符官方标准六
- java 2d 绘图教程_Java标准教程:Java 2D绘图--第2章 从绘图开始
- AppVerifier的功能和原理
- 产品部门四大角色——PM/PD/UE/UI
- android 修复工具,牛学长安卓手机修复工具(Reiboot for Android)
- DiskGeniux无损分区
- windows10未安装画图工具解决办法
- 夜深人静写算法(二) - 动态规划
- 华为手机桌面有计算机的数字,华为手机桌面上信息图标上显示的数字角标如何关闭...
- 尝试使用Visual studio编写Android程序C++的跨平台开发Android
- linux内核5.8.1,Linus Torvalds宣布大规模更新Linux内核5.8
- 知识管理:营建学习型团队
- xsy3320 string
- HCM 初学 ( 二 ) - 信息类型
- MySQL性能调优必知:Performance Schema引擎的配置与使用
- C++编程(二):CURL错误码及含义
热门文章
- linux驱动导出文件属性,linux驱动入门——模块参数和导出符号
- maya怎么保持2u_【肘进健康】手肘超伸怎么练?
- C++知识点8——函数的返回值
- 记录一下从标定模型中读取参数
- get_sheet_of_light_result_object_model_3d算子说明
- oracle插入表为文件,将文本文件插入Oracle表中
- matlab jdbc mysql_Matlab连接MySQL(ODBC及JDBC驱动)
- Coding and Paper Letter(二十四)
- 《程序员代码面试指南》第二章 链表问题 构造链表和节点的实体
- Silverlight2 边学边练 之五 视频