你还在被触摸事件困扰吗?看看这篇吧

本文深入探讨了Android中触摸事件的处理流程,通过六个情景分析了事件分发的细节,包括长按事件、滑动事件以及事件拦截等。文章详细解释了ViewGroup的事件分发机制,涉及ACTION_DOWN事件的重要性、TouchTarget链表的管理以及如何防止事件穿透等问题,旨在帮助开发者更好地理解和解决触摸事件相关问题。
摘要由CSDN通过智能技术生成

image

在CoorChice的这篇文章《原来Android触控机制竟是这样的?》http://www.jianshu.com/p/b7cef3b3e703 中,CoorChice简要的介绍了一下Android中触摸事件的大致流程。于做应用而言,实际我们只需要清楚文中蓝色那部分流程就行。

本篇文章中,CoorChice将针对这个流程进行分析。为什么这个流程会是这个样子?以及这个流程中有什么特别之处。

情景分析

image

上图中:

  1. VG表示最底层父。
  2. VG-1包含一个子View V-1-1。
  3. VG-1、V-2、V-3都是VG的子View,且V-3是覆盖在V-2上的。

假设VG-1的onInterceptTouchEvent()中,会拦截上下滑动的事件,而不会拦截左右滑动事件。V-1-1能够处理左右滑动事件。

情境一 在point-1点长按V-1-1触发长按事件的过程中,发生了左右滑动,长按事件还能触发吗?

在View的onTouchEvent()的默认实现中,长按事件是在接收到ACTION_DOWN之后,post一个延时500ms的Runnable实现对象。如果顺利的话,500ms后View会调用给它设置的onLongClickListeneronLongClick()方法,如果这个方法返回了true,则记录下长按事件已经触发了。

在等待的500ms中,View还会不断的接收到TouchEvent事件,触发ACTION_MOVE,如果触发了滑动(这里假设触发的是左右滑动事件),就会移除前面post的Runnable实现对象。所以长按事件就不会触发。

情景二 接着上面左右滑动的过程,如果再发生上下滑动,VG-1的上下滑动会触发吗?

由于每个事件的传递都会经过VG-1的dispatchTouchEvent()方法才能分发到V-1-1的onTouchEvent()中,而一般情况下,在VG-1的dispatchTouchEvent()中每次都会询问onInterceptTouchEvent()要不要拦截事件,如果拦截了,事件自然就会再往下分发了。

所以,在V-1-1左右滑动中,再发生上下滑动,事件将会被VG-1拦截,然后触发它的上下滑动。

情景三 接着情景二,如果再次发生左右滑动,V-1-1还能触发左右滑动吗?

VG-1一旦拦截了触摸事件,就会给所有TouchTarget中的子View模拟分发一个ACTION_CANCEL事件,将原本能接收到触摸事件的子View排除在本次事件流之外。并且会清空TouchTarget队列。这样后续的事件将会跳过onInterceptTouchEvent()判断,直接标记为拦截状态,进而分发到VG-1自己的onTouchEvent()中去。

所以,这种情况下V-1-1将不能再接收到本次事件流中的事件了。就是说,只要父View拦截了事件,子View就再也拦截不到事件了,并且后续的事件不管父View拦不拦截,都会往父View的onTouchEvent()中分发。

情景四 在情景一的分析中我们知道,在V-1-1接收到ACTION_DOWN时会post一个延迟Runnable等待触发长按事件。那么是不是即使我们让V-1-1在接收到ACTION_DOWN时返回false,等到500ms后,长按事件还会触发吗?

我们知道,如果V-1-1的onTouchEvent()返回了false,表示它不处理事件,那么它将不会保存到VG-1的TouchTarget队列中。就是说即使下一个事件将被VG-1直接拦截,也不会有ACTION_CANCEL事件分发到V-1-1中。而长按事件的Runnable除了上面说的情况会被移除外,收到ACTION_CANCEL时也会移除。所以这种情况下V-1-1的长按事件Runnable是没办法被移除的,只要到500ms,它的长按事件就会触发。

情景五 在情景一 的基础上,如果V-1-1向右滑动了一定距离后onTouchEvent()返回false,当在point-1再滑动一定距离,然后让onTouchEvent()再次返回true。那么V-1-1还能继续接收到后续是事件吗?

在本情景下,当V-1-1的onTouchEvent()返回false后,由于没有触发上下滑动,所以VG-1不会拦截触摸事件。再就是前面已经把V-1-1放到了VG-1的触摸目标队列中,所以触摸事件仍然能够分发到V-1-1中,即使它返回了false。

所以,这种情况下,V-1-1始终能接收到事件。

情景六 触摸point-2点,如果ACTION_DOWN时,V-3的onTouchEvent()返回false,V-2的的onTouchEvent()返回true,V-3设置为随后再有事件分发过来时返回true,V-3还能继续处理后续事件吗?

经过上面几个情景的分析我们知道,在ACTION_DOWN时VG-1会从后往前遍子View,发现有子View消费了事件的,就将它存入TouchTarget中,然后不在进行后面的遍历。在这个情景中,V-3在ACTION_DOWN时不处理事件,这意味着它就没有被存到TouchTarget中,而后续的事件不会再进行遍历,而是直接分发到TouchTarget对应的子View中,所以本次事件流将会在V-2中处理。而V-3将不能再收到后面的事件。

这就是为什么有的View它默认实现的onTouchEvent()会在ACTION_DOWN时返回false,从而出现"点击穿透"的Bug。

image

原理分析

上一节CoorChice通过六个情景再现了一些我们平时会遇到的触摸事件场景。下面CoorChice将从原理上分析,这些情景中的结果是怎么来的?

其实要理解上层的触摸事件机制,关键就在于ViewGroup的dispatchTouchEvent(),它实现了Android事件分发的一套逻辑,当然如果我们有更好的方法,也可以自己来处理这个过程!但是现在我们还是基于Android的逻辑来看吧。

下面的逻辑基本都是ViewGroup的dispatchTouchEvent()中的,期间也会涉及到相关方法的分析,如遇到没写方法名的,表明这段代码是在ViewGroup的dispatchTouchEvent()中的。

1. 判断要不要分发本次触摸事件

...
if (onFilterTouchEventForSecurity(ev)) {
    // 获取MotionEvent的事件类型
    final int action = ev.getAction();
    // actionMasked能够区分出多点触控事件
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    ...

首先通过onFilterTouchEventForSecurity(ev)检查要不要分发本次事件,检查通过了才会进行分发,走if中的逻辑。否则就放弃对本次事件的处理。

我们看看这里是依据什么条件来判断要不要进行分发的。

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    if (// 先检查View有没有设置被遮挡时不处理触摸事件的flag
        (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            // 再检查受到该事件的窗口是否被其它窗口遮挡
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}

看代码注释,这里就是检查了两个条件,看这个事件对于该View来说是不是安全的。

第一个条件FILTER_TOUCHES_WHEN_OBSCURED可以通过在xml文件中的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值