目录

  • 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机制及作用相关推荐

  1. Android事件分发之ACTION_MOVE与ACTION_UP的传递机制

    目录 引言 ACTION_MOVE与ACTION_UP的传递机制 mFirstTouchTarget作用 mFirstTouchTarget为什么是链表结构 引言 关于Android事件分发机制网上相 ...

  2. Android事件分发之ViewGroup篇 -- ViewGroup的dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent之间关系

    Android事件分发之ViewGroup篇(FatherViewGroup) – ViewGroup的dispatchTouchEvent.onTouchEvent.onInterceptTouch ...

  3. Android 事件分发,分发机制

    1. 基础认知 1.1 事件分发的对象是谁? 答:点击事件(Touch事件) 定义 当用户触摸屏幕时(View 或 ViewGroup派生的控件),将产生点击事件(Touch事件) Touch事件的相 ...

  4. Android 事件分发面试题2

    问:LinearLayout 里面嵌套Button,LinearLayout 设置了onClick 事件,但是Button 没有设置点击事件,当我点击Button 的时候,LinearLayout 的 ...

  5. 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 七 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  6. 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  7. 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  8. 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  9. 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

最新文章

  1. 怎么安装python_零基础入门必看篇:浅析python,PyCharm,Anaconda三者之间关系
  2. 动态配置流处理-BetterCloud如何使用Flink构建报警系统
  3. ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记...
  4. LeetCode 1255. 得分最高的单词集合(回溯)
  5. 漫画 | 面试的我 VS 真实的我
  6. 【Java】总结Java数组的拷贝和输出
  7. matplotlib —— fill between
  8. 204.计数质数 (力扣leetcode) 博主可答疑该问题
  9. kubernetes 核心组件的运行机制
  10. Dropping Balls
  11. 苹果Mac怎样清除dns缓存?
  12. 计算机考研用python_2014北邮计算机考研复试上机题解(上午+下午)
  13. SSD:Single Shot MultiBox Detector解读
  14. 云栖大会人脸识别闸机【技术亮点篇4】--户外闸机高20%的抗撞击能力
  15. Xshell连接云服务器并连接宝塔面板(天翼云为例)
  16. Vue3入门到精通--reactive以及reactive相关函数
  17. Haproxy基础知识 -运维小结
  18. iOS 应用闪退的原因
  19. 高德地图开发:英文地图的实现方式
  20. 指标归因平台建设思路

热门文章

  1. 石子合并——最经典的dp问题
  2. 二叉树的前序,中序,后序遍历Java实现
  3. linux mac终端快捷键设置,mac shell终端编辑命令行快捷键
  4. 共振峰估计实验MATLAB
  5. 1. Pandas 导入导出数据
  6. CodeForces - 1213A Chips Moving (思维 数学)
  7. java 工作两年的简历_工作经验只有两年的Java开发,简历中需要写学校经历吗?...
  8. Oracle打补丁步骤
  9. 不花钱一样可以引流获客?这6招功劳不小
  10. 刘邦韩信java_刘邦为什么叫韩信雏儿 刘邦杀韩信后悔了吗