Android中ACTION_CANCEL的触发机制与滑出子view的情况

目录
  • ACTION_CANCEL的触发时机
    • 1,父view拦截事件
    • 2,ACTION_DOWN初始化操作
    • 3,在子View处理事件的过程中被从父View中移除时
    • 4,子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时
  • 滑出子View区域会发生什么?
  • 结论:

看完本文你将了解:

  • ACTION_CANCEL的触发时机
  • 滑出子View区域会发生什么?为什么不响应onClick()事件

首先看一下官方的解释:

/**
 * Constant for {@link #getActionMasked}: The current gesture has been aborted.
 * You will not receive any more points in it.  You should treat this as
 * an up event, but not perform any action that you normally would.
 */
public static final int ACTION_CANCEL           = 3;

说人话就是:当前的手势被中止了,你不会再收到任何事件了,你可以把它当做一个ACTION_UP事件,但是不要执行正常情况下的逻辑。

ACTION_CANCEL的触发时机

有四种情况会触发ACTION_CANCEL:

  • 在子View处理事件的过程中,父View对事件拦截
  • ACTION_DOWN初始化操作
  • 在子View处理事件的过程中被从父View中移除时
  • 子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时

1,父view拦截事件

首先要了解ViewGroup什么情况下会拦截事件,Look the Fuck Resource Code:

/**
 * {@inheritDoc}
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
	...

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        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) {
            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;
        }
        ...
    }
    ...
}

有两个条件

  • MotionEvent.ACTION_DOWN事件或者mFirstTouchTarget非空也就是有子view在处理事件
  • 子view没有做拦截,也就是没有调用ViewParent#requestDisallowInterceptTouchEvent(true)

如果满足上面的两个条件才会执行onInterceptTouchEvent(ev)
如果ViewGroup拦截了事件,则intercepted变量为true,接着往下看:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        ...

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 当mFirstTouchTarget != null,也就是子view处理了事件
                // 此时如果父ViewGroup拦截了事件,intercepted==true
                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;
        }

        ...

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            ...
        } else {
            // 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) {
                    ...
                } else {
                    // 判断一:此时cancelChild == true
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;

					// 判断二:给child发送cancel事件
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    ...
                }
                ...
            }
        }
        ...
    }
    ...
    return handled;
}

以上判断一处cancelChild为true,然后进入判断二中一看究竟:

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设置成ACTION_CANCEL
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            ...
        } else {
            // 分发给child
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    ...
}

当参数cancel为ture时会将event设置为MotionEvent.ACTION_CANCEL,然后分发给child。

2,ACTION_DOWN初始化操作

public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 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.
            // 取消并清除所有的Touch目标
            cancelAndClearTouchTargets(ev);
            resetTouchState();
    	}
    	...
    }
    ...
}

系统可能会由于App切换、ANR等原因丢失了up,cancel事件。

因此需要在ACTION_DOWN时丢弃掉所有前面的状态,具体代码如下:

private void cancelAndClearTouchTargets(MotionEvent event) {
    if (mFirstTouchTarget != null) {
        boolean syntheticEvent = false;
        if (event == null) {
            final long now = SystemClock.uptimeMillis();
            event = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            syntheticEvent = true;
        }

        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            resetCancelNextUpFlag(target.child);
            // 分发事件同情况一
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
        ...
    }
}

PS:在dispatchDetachedFromWindow()中也会调用cancelAndClearTouchTargets()

3,在子View处理事件的过程中被从父View中移除时

public void removeView(View view) {
    if (removeViewInternal(view)) {
        requestLayout();
        invalidate(true);
    }
}

private boolean removeViewInternal(View view) {
    final int index = indexOfChild(view);
    if (index >= 0) {
        removeViewInternal(index, view);
        return true;
    }
    return false;
}

private void removeViewInternal(int index, View view) {

    ...
    cancelTouchTarget(view);
	...
}

private void cancelTouchTarget(View view) {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (target.child == view) {
            ...
            // 创建ACTION_CANCEL事件
            MotionEvent event = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            分发给目标view
            view.dispatchTouchEvent(event);
            event.recycle();
            return;
        }
        predecessor = target;
        target = next;
    }
}

4,子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时

在情况一种的两个判断处:

// 判断一:此时cancelChild == true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;

// 判断二:给child发送cancel事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
    target.child, target.pointerIdBits)) {
    handled = true;
}

resetCancelNextUpFlag(target.child) 为true时同样也会导致cancel,查看代码:

/**
 * Indicates whether the view is temporarily detached.
 *
 * @hide
 */
static final int PFLAG_CANCEL_NEXT_UP_EVENT        = 0x04000000;

private static boolean resetCancelNextUpFlag(View view) {
    if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
        view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
        return true;
    }
    return false;
}

根据注释大概意思是,该view暂时detached,detached是什么意思?就是和attached相反的那个,具体什么时候打了这个标记,我觉得没必要深究。

以上四种情况最重要的就是第一种,后面的只需了解即可。

滑出子View区域会发生什么?

了解了什么情况下会触发ACTION_CANCEL,那么针对问题:滑出子View区域会触发ACTION_CANCEL吗?这个问题就很明确了:不会。

实践是检验真理的唯一标准,代码撸起来:

public class MyButton extends androidx.appcompat.widget.AppCompatButton {

	@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                LogUtil.d("ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtil.d("ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtil.d("ACTION_UP");
                break;
            case MotionEvent.ACTION_CANCEL:
                LogUtil.d("ACTION_CANCEL");
                break;
        }
        return super.onTouchEvent(event);
    }
}

一波操作以后日志如下:

(MyButton.java:32) -->ACTION_DOWN
(MyButton.java:36) -->ACTION_MOVE
(MyButton.java:36) -->ACTION_MOVE
(MyButton.java:36) -->ACTION_MOVE
(MyButton.java:36) -->ACTION_MOVE
(MyButton.java:36) -->ACTION_MOVE
(MyButton.java:39) -->ACTION_UP

滑出view后依然可以收到ACTION_MOVEACTION_UP事件。

为什么有人会认为滑出view后会收到ACTION_CANCEL呢?

我想是因为滑出view后,view的onClick()不会触发了,所以有人就以为是触发了ACTION_CANCEL

那么为什么滑出view后不会触发onClick呢?再来看看View的源码:

在view的onTouchEvent()中:

case MotionEvent.ACTION_MOVE:
    // Be lenient about moving outside of buttons
	// 判断是否超出view的边界
    if (!pointInView(x, y, mTouchSlop)) {
        // Outside button
        if ((mPrivateFlags & PRESSED) != 0) {
            // 这里改变状态为 not PRESSED
            // Need to switch from pressed to not pressed
            mPrivateFlags &= ~PRESSED;
        }
    }
    break;

case MotionEvent.ACTION_UP:
    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
    // 可以看到当move出view范围后,这里走不进去了
    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
        ...
        performClick();
        ...
    }
    mIgnoreNextUpEvent = false;
    break;

1,在ACTION_MOVE中会判断事件的位置是否超出view的边界,如果超出边界则将mPrivateFlags置为not PRESSED状态。
2,在ACTION_UP中判断只有当mPrivateFlags包含PRESSED状态时才会执行performClick()等。
因此滑出view后不会执行onClick()

结论:

  • 滑出view范围后,如果父view没有拦截事件,则会继续受到ACTION_MOVEACTION_UP等事件。
  • 一旦滑出view范围,view会被移除PRESSED标记,这个是不可逆的,然后在ACTION_UP中不会执行performClick()等逻辑。

到此这篇关于Android中ACTION_CANCEL的触发机制与滑出子view的情况的文章就介绍到这了,更多相关Android ACTION_CANCEL内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android如何获取子View的位置及坐标详解

    一.View 1.1.View 概述 视图 (View) 是一个容器,专门负责布局.表现为显示在屏幕上的各种视图,如 TextView.LinearLayout 等. 1.2.View 分类 View 主要分为两类,具体如下表格所示: 类别 示例 特点 单一视图 即一个 View,如 TextView.EditText 不包含子View 视图组 即多个 View 组成的 ViewGroup,如 RelativeLayout 包含子View 1.3.View 类简介 View 类是 Android

  • Android开发中Intent.Action各种常见的作用汇总

    本文介绍Android中Intent的各种常见作用. 1 Intent.ACTION_MAIN String: android.intent.action.MAIN 标识Activity为一个程序的开始.比较常用. Input:nothing Output:nothing <activity android:name=".Main" android:label="@string/app_name"> <intent-filter> <a

  • Android中父View和子view的点击事件处理问题探讨

    android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解. 一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE...->ACTION_MOVE->ACTION_UP 当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是ViewG

  • Android中ACTION_CANCEL的触发机制与滑出子view的情况

    目录 ACTION_CANCEL的触发时机 1,父view拦截事件 2,ACTION_DOWN初始化操作 3,在子View处理事件的过程中被从父View中移除时 4,子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时 滑出子View区域会发生什么? 结论: 看完本文你将了解: ACTION_CANCEL的触发时机 滑出子View区域会发生什么?为什么不响应onClick()事件 首先看一下官方的解释: /** * Constant for {@link #getActi

  • 聊聊Android中的事件分发机制

    View事件分发机制的本质就是就是MotionEvent事件的分发过程,即MotionEvent产生后是怎样在View之间传递及处理的. 首先介绍一下什么是MotionEvent.所谓MotionEvent,即用户手指触碰手机屏幕时产生的一系列触摸事件.典型的触摸事件有: ACTION_DOWN:手指刚接触屏幕的一瞬间. ACTION_MOVE:手指在屏幕上滑动. ACTION_UP:手指离开屏幕的一瞬间. ACTION_CANCLE:当前事件序列终止. 一个事件序列一般都是以DOWN事件开始,

  • 详细分析Android中onTouch事件传递机制

    onTach介绍 ontach是Android系统中整个事件机制的基础.Android中的其他事件,如onClick.onLongClick等都是以onTach为基础的. onTach包括从手指按下到离开手机屏幕的整个过程,在微观形式上,具体表现为action_down.action_move和action_up等过程. onTach两种主要定义形式如下: 1.在自定义控件中,常见的有重写onTouchEvent(MotionEvent ev)方法.如在开发中经常可以看到重写的onTouchEv

  • Android中如何利用AIDL机制调用远程服务

    在Android中,每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢?显然, Java中是不支持跨进程内存共享的.因此要传递对象,需要把对象解析成操作系统能够理解的数据格式,以达到跨界对象访问的目的.在JavaEE中,采用RMI通过序列化传递对象.在Android中,则采用AIDL(Android Interface DefinitionLanguage:接口描述语言)方式实现. AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,实现Andro

  • Android中微信小程序开发之弹出菜单

    先给大家展示下效果图,具体效果图如下所示: 具体代码如下所示: 1.index.js //index.js //获取应用实例 var app = getApp() Page({ data: { isPopping: false,//是否已经弹出 animationPlus: {},//旋转动画 animationcollect: {},//item位移,透明度 animationTranspond: {},//item位移,透明度 animationInput: {},//item位移,透明度

  • Android 中读取SD卡文件时抛出NullPointerException错误解决办法

    Android 中读取SD卡文件时抛出NullPointerException错误解决办法 相关源码: package com.example.musicplayer; import java.io.File; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import

  • 深入理解Android中的Handler异步通信机制

    一.问题:在Android启动后会在新进程里创建一个主线程,也叫UI线程(非线程安全)这个线程主要负责监听屏幕点击事件与界面绘制.当Application需要进行耗时操作如网络请求等,如直接在主线程进行容易发生ANR错误.所以会创建子线程来执行耗时任务,当子线程执行完毕需要通知UI线程并修改界面时,不可以直接在子线程修改UI,怎么办? 解决方法:Message Queue机制可以实现子线程与UI线程的通信. 该机制包括Handler.Message Queue.Looper.Handler可以把

  • Android中的Permission权限机制介绍

    Android 通过在每台设备上实施了基于权限的安全策略来处理安全问题,采用权限来限制安装应用程序的能力.当某个权限与某个操作和资源对象绑定在一起,我们必须获得这个权限才能在对象上执行操作.由于Android设计本身就是为Android开发人员着想,所以一切权限许可权由用户决定而不是手机制造商和平台提供商,但这不得不带来了开发者滥用权限,黑客通过权限来进行恶意行为的风险,所以作为静态分析一个app是否为恶意软件的第一道关,获取并了解Android Permission权限意义是十分重大的. 权限

  • Android中Intent机制详解及示例总结(总结篇)

    最近在进行android开发过程中,在将 Intent传递给调用的组件并完成组件的调用时遇到点困难,并且之前对Intent的学习也是一知半解,最近特意为此拿出一些时间,对Intent部分进行了系统的学习并进行了部分实践,下面将自己的学习及Intent知识进行了详细的归纳整理,希望能帮助到同样遇到相同问题的博友. 下面是Intent介绍.详解及Intent示例总结: 一.Intent介绍: Intent的中文意思是"意图,意向",在Android中提供了Intent机制来协助应用间的交互

  • Android中Binder IPC机制介绍

    目录 前言 一.Binder是什么? 二.为什么要使用Binder 三.IPC机制原理 传统IPC机制如何实现跨进程通信 Binder IPC机制原理 小结 前言 记得刚开始做Andorid那会,面试时最怕被问到Binder,就感觉战战兢兢不知道从什么地方说起,导致后来一直有一种恐惧感.当然现在没有这种感觉了,但是这块知识点一直模模糊糊的,最近在学Andorid framework课程,借此机会简单总结下其中Binder相关知识点. 一.Binder是什么? Binder是Android中一种进

随机推荐