Android事件分发之ACTION_CANCEL机制及作用
目录
- ACTION_CANCEL产生场景
- ACTION_CANCEL作用
- FLAG_DISALLOW_INTERCEPT的作用
如果要查看ACTION_MOVE与ACTION_UP的事件传递机制,查看Android事件分发之ACTION_MOVE与ACTION_UP的传递机制
ACTION_CANCEL产生场景
在阅读ViewGroup事件分发相关源码过程中,有时候会见到ACTION_CANCEL
这一事件。那么这一事件是如何产生的呢?按照网上的说法,当手指从当前view移出后,当前view就会收到ACTION_CANCEL这一事件,这一定是正确的吗?下面我们来看两个例子:
例子1:
import android.content.Context
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEventclass CustomViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {var hasInterceptMoveEvent = falseoverride fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {// Log.d("TAG", "${ev?.action}:CustomViewGroup onInterceptTouchEvent")if (!hasInterceptMoveEvent && ev?.action == MotionEvent.ACTION_MOVE) {hasInterceptMoveEvent = truereturn true}return false}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.d("TAG", "${getAction(ev?.action)}:CustomViewGroup dispatchTouchEvent")return super.dispatchTouchEvent(ev)}}
我们自定义一个ViewGroup,覆盖它的onInterceptTouchEvent
方法和dispatchTouchEvent
方法。在onInterceptTouchEvent
方法中我们只拦截了一次MotionEvent.ACTION_MOVE
事件。hasInterceptMoveEvent
用于控制只拦截一次。而在dispatchTouchEvent
方法中,我们打印出当前ViewGroup处理的事件。看下getAction
方法定义:
fun getAction(action: Int?): String {return when (action) {MotionEvent.ACTION_DOWN -> "MotionEvent.ACTION_DOWN"MotionEvent.ACTION_MOVE -> "MotionEvent.ACTION_MOVE"MotionEvent.ACTION_UP -> "MotionEvent.ACTION_UP"MotionEvent.ACTION_CANCEL -> "MotionEvent.ACTION_CANCEL"else -> "OTHER"}
}
其实就是根据Action对应的Int值转化为字符串,让我们的Log更加直观。
而在ViewGroup内部,我们放置了一个自定义Button。代码如下:
class CusButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Button(context, attrs, defStyleAttr) {override fun dispatchTouchEvent(event: MotionEvent?): Boolean {Log.d("TAG", "${getAction(event?.action)}:CusButton dispatchTouchEvent")return true}
}
这个自定义Button只是将当前dispatchTouchEvent
方法收到的事件打印出来。
现在我们进行如下操作:
手指按住button,然后移动,移出button外,然后松开。我们看下打印出的Log:
04-23 15:13:48.553 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.553 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CusButton dispatchTouchEvent
04-23 15:13:48.574 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.574 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_CANCEL:CusButton dispatchTouchEvent
04-23 15:13:48.591 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.607 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.624 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.642 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.658 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.674 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.691 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.708 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.724 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.741 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.758 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.775 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.791 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.808 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.825 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.841 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.859 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.866 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.867 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CustomViewGroup dispatchTouchEvent
我们看到CusButton确实收到了一个ACTION_CANCEL
事件,并且在这个事件之后,CusButton并没有收到任何事件。所以我们大概能够猜到:如果ViewGroup拦截了Move事件,那么这个Move事件将会转化为Cancel事件传递给子view。
例子2:
如果我们的ViewGroup不拦截Move事件,那么是否也会产生Cancel事件呢?
我们修改一下CustomViewGroup
源码:
class CustomViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {// var hasInterceptMoveEvent = falseoverride fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {// Log.d("TAG", "${ev?.action}:CustomViewGroup onInterceptTouchEvent")
// if (!hasInterceptMoveEvent && ev?.action == MotionEvent.ACTION_MOVE) {// hasInterceptMoveEvent = true
// return true
// }return false}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.d("TAG", "${getAction(ev?.action)}:CustomViewGroup dispatchTouchEvent")return super.dispatchTouchEvent(ev)}}
即onInterceptTouchEvent
方法不拦截事件。同样的操作我们再来看下Log:
04-23 15:29:24.089 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.089 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CusButton dispatchTouchEvent
04-23 15:29:24.104 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.105 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.121 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.121 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.139 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.139 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.154 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.155 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.171 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.171 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.188 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.188 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.205 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.205 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.221 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.221 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.233 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.233 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.250 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.250 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.268 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.268 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.275 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CusButton dispatchTouchEvent
我们可以看到Button不会收到Cancel事件。即时手指滑出了button,仍然可以收到Move和Up事件。
ACTION_CANCEL作用
我们知道如果某一个子View处理了Down事件,那么随之而来的Move和Up事件也会交给它处理。但是交给它处理之前,父View还是可以拦截事件的,如果拦截了事件,那么子View就会收到一个Cancel事件,并且不会收到后续的Move和Up事件。
下面我们通过源码验证上面这段话:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {boolean handled = false;final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {//----------源码1-----------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;}// Check for cancelation.final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {// ...}// Dispatch to touch targets.if (mFirstTouchTarget == null) {// ...} else {// ---------------源码2----------------// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;//-------------- 源码3---------if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}//-------------- 源码4---------if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}return handled;
}
这段代码进行了一定的精简,但是大致流程还是很清楚的。当子view处理完Down事件之后,mFirstTouchTarget
不为null,那么是可以走到源码1处的,如果子view没有对ViewGroup进行不拦截的设置,那么disallowIntercept
为false,此时会走到onInterceptTouchEvent
方法,如果我们拦截了Move事件,那么onInterceptTouchEvent
返回true。也就是说intercepted
为true;
此时会走到源码2处,遍历每一个将要处理事件的view,alreadyDispatchedToNewTouchTarget
表示事件是否已经交给view处理,此时当然是false。到源码3处时,由于intercepted
为true,所以cancelChild
为true,我们看下dispatchTransformedTouchEvent
方法:
/*** Transforms a motion event into the coordinate space of a particular child view,* filters out irrelevant pointer ids, and overrides its action if necessary.* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.*/private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;// Canceling motions is a special case. We don't need to perform any transformations// or filtering. The important part is the action, not the contents.final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}// 。。。}
注意,走到dispatchTransformedTouchEvent
时,cancel这个参数为true。通过event.setAction(MotionEvent.ACTION_CANCEL);
这句话将MotionEvent的Action设置为了ACTION_CANCEL
,并且交给子view处理。
通过这里我源码我们知道了ACTION_CANCEL
的产生过程。那么为什么产生ACTION_CANCEL
后,子view无法收到后续事件了呢?
在源码4处,有这样一段代码:
if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}
cancelChild为true,会将当前target节点从链表中删除。那么后续事件到来时,view在mFirstTouchTarget
链表中不存在,自然就不会交给它处理了。
FLAG_DISALLOW_INTERCEPT的作用
我们通过上面的代码可以看出,即时是MOVE和UP事件,在传递给子View之前也是可以通过ViewGroup的onInterceptTouchEvent
方法拦截的,如果拦截了,那么该事件就会变成Cancel事件传递给子view。
那么是否有办法,子view不让ViewGroup拦截时间呢?
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;}
可以看到disallowIntercept
变量为true的时候,会跳过onInterceptTouchEvent
方法。换句话如果设置了FLAG_DISALLOW_INTERCEPT
这个flag,那么ViewGoup则不会拦截Move和Up事件。
我们再找到设置FLAG_DISALLOW_INTERCEPT
这个flag的地方。
@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are tooreturn;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}// Pass it up to our parentif (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}}
这段代码,充分展示了位运算的强大即高效。子View可以通过设置requestDisallowInterceptTouchEvent(true)
来达到禁止父ViewGroup拦截事件的目的。
但是需要注意的是,FLAG_DISALLOW_INTERCEPT
这个flag无法对Down事件生效。因为在Down时,会清空FLAG_DISALLOW_INTERCEPT
。
// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev);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;}
Android事件分发之ACTION_CANCEL机制及作用相关推荐
- Android事件分发之ACTION_MOVE与ACTION_UP的传递机制
目录 引言 ACTION_MOVE与ACTION_UP的传递机制 mFirstTouchTarget作用 mFirstTouchTarget为什么是链表结构 引言 关于Android事件分发机制网上相 ...
- Android事件分发之ViewGroup篇 -- ViewGroup的dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent之间关系
Android事件分发之ViewGroup篇(FatherViewGroup) – ViewGroup的dispatchTouchEvent.onTouchEvent.onInterceptTouch ...
- Android 事件分发,分发机制
1. 基础认知 1.1 事件分发的对象是谁? 答:点击事件(Touch事件) 定义 当用户触摸屏幕时(View 或 ViewGroup派生的控件),将产生点击事件(Touch事件) Touch事件的相 ...
- Android 事件分发面试题2
问:LinearLayout 里面嵌套Button,LinearLayout 设置了onClick 事件,但是Button 没有设置点击事件,当我点击Button 的时候,LinearLayout 的 ...
- 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 七 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
最新文章
- 怎么安装python_零基础入门必看篇:浅析python,PyCharm,Anaconda三者之间关系
- 动态配置流处理-BetterCloud如何使用Flink构建报警系统
- ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记...
- LeetCode 1255. 得分最高的单词集合(回溯)
- 漫画 | 面试的我 VS 真实的我
- 【Java】总结Java数组的拷贝和输出
- matplotlib —— fill between
- 204.计数质数 (力扣leetcode) 博主可答疑该问题
- kubernetes 核心组件的运行机制
- Dropping Balls
- 苹果Mac怎样清除dns缓存?
- 计算机考研用python_2014北邮计算机考研复试上机题解(上午+下午)
- SSD:Single Shot MultiBox Detector解读
- 云栖大会人脸识别闸机【技术亮点篇4】--户外闸机高20%的抗撞击能力
- Xshell连接云服务器并连接宝塔面板(天翼云为例)
- Vue3入门到精通--reactive以及reactive相关函数
- Haproxy基础知识 -运维小结
- iOS 应用闪退的原因
- 高德地图开发:英文地图的实现方式
- 指标归因平台建设思路
热门文章
- 石子合并——最经典的dp问题
- 二叉树的前序,中序,后序遍历Java实现
- linux mac终端快捷键设置,mac shell终端编辑命令行快捷键
- 共振峰估计实验MATLAB
- 1. Pandas 导入导出数据
- CodeForces - 1213A Chips Moving (思维 数学)
- java 工作两年的简历_工作经验只有两年的Java开发,简历中需要写学校经历吗?...
- Oracle打补丁步骤
- 不花钱一样可以引流获客?这6招功劳不小
- 刘邦韩信java_刘邦为什么叫韩信雏儿 刘邦杀韩信后悔了吗