三个维度,不太一样的View事件分发机制(滑动冲突)

本文是Android View事件分发机制系列的第三篇,主要探讨滑动冲突的解决。分析了滑动冲突的场景,包括方向不一致、方向一致和多层嵌套的情况,并提出了处理原则。介绍了两种解决滑动冲突的方法:外层直接拦截法和内层阻止拦截法,通过示例代码详细解释了这两种方法的实现。
摘要由CSDN通过智能技术生成

每一个Android开发者,都绕不开View的事件分发,理解好View的事件分发机制,有利于我们解决各种与设备触摸交互的问题,同时也利于我们实现更复杂、炫酷的自定义View效果。


关于Android View的事件分发机制,笔者打算分为三篇文章来描述,分别为:

(1)Android View事件分发机制之概念理论篇

(2)Android View事件分发机制之源码解读篇

(3)Android View事件分发机制之滑动冲突实战解决篇


本文是第三篇:Android View事件分发机制之滑动冲突解决篇


滑动冲突场景分析


从上一篇:Android View事件分发机制之源码解读篇,我们已经知道为什么会产生滑动冲突了。


这里再简单回顾一下:

当子View消耗了ACTION_DOWN事件,那么同一个事件序列的其他事件都应该由子View消耗处理。但如果在某些时刻,这个事件序列的某个ACTION_MOVE被父View拦截了,那么后续的所有事件就不会传给子View了,子View也就没有办法完成响应整个事件序列,这时便发生了滑动冲突。


而ViewGroup默认是不会拦截ACTION_DOWN以外的事件的,所以我们平时常使用的那几个Layout,如RelativeLayout、LinearLayout、FrameLayout均继承了ViewGroup,但又没有重写onInterceptTouchEvent方法,所以这几个Layout是不会出现滑动冲突的。


而对于ScrollView、ListView、RecycleView,它们都重写了onInterceptTouchEvent方法,并在某种条件下,拦截下了ACTION_MOVE事件,因此对于触摸在子View上而言,就出现了滑动冲突。又或者是我们自己自定义的Layout,继承ViewGroup后也重写onInterceptTouchEvent,并拦截ACTION_MOVE,也是有可能发生滑动冲突的。


如果从与设备交互的角度去分析,滑动冲突产生的原因主要是因为:

界面内只要存在内外两层(即子View、父View)同时可以滑动,就会产生滑动冲突。

而内外两层可同时滑动的场景主要有以下三个:

  • 外部滑动方向和内部滑动方向不一致
  • 外部滑动方向和内部滑动方向一致
  • 多层View的嵌套中,同时存在外部与内部滑动方向一致和不一致的情况

分别用三个图表示三个场景,简单明了:

外部滑动方向和内部滑动方向不一致

滑动冲突场景一

外部滑动方向和内部滑动方向一致

滑动冲突场景二

多层View的嵌套中,同时存在外部与内部滑动方向一致和不一致

滑动冲突场景三


滑动冲突的处理原则


从上面三个示例图,我们可以很直白的认识到什么情况下会产生滑动冲突。那对于上述三种滑动冲突的场景,我们是基于什么样的原则去处理呢?


对于子父View滑动方向不一致的情况,(这里先假设父View是水平方向滑动,子View是竖直方向滑动)正常的交互应该是,当我们手势是水平方向滑动时,由View响应;当手势是竖直方向滑动时,由子View响应。那么这样子,它的处理原则就很明了了:

当子父View滑动方向不一致时,我们可以根据手势的滑动方向来决定是由父View响应还是由子View响应。如果手势方向不是水平或不是竖直的,我们也可以通过同一个时刻,对比x轴上和y轴上的移动距离来决定当前手势属于水平滑动还是竖直滑动。


这种场景,很经典的就是ViewPager、Fragment、ListView或者RecycleView结合做切页效果了。但是我们在使用这三种View做切页效果时,却没有遇到滑动冲突的情况,其实是因为ViewPager内部已经处理了。


对于子父View滑动方向相同的情况,这种时候就比较特殊,我们无法根据滑动的方向、角度、距离、速度差等因素来判断到底由父View响应还是由子View响应,这时我们就得在业务需求上找突破点,根据业务需求来决定什么情况下由父View响应,什么情况下由子View响应。比如:(假设子父View都是上下滑动)如果手势是放在子View上,滑动时就是子View响应,否则就是父View响应。


对于子父View同时存在滑动方向相同和不同的情况,也是无法根据滑动的方向、角度、距离、速度差等因素来决定是由父View响应还是由子View响应,这时我们也得根据具体需求来处理。


但不论是哪一种情况,滑动冲突问题都是能够解决的,接下来我们来说一下解决滑动冲突有哪些方式。


滑动冲突的解决方式


解决滑动冲突,离不开一个宗旨:

因为事件的分发是由外到内,即先到父View再到子View。那么如果是父View需要处理事件,父View就进行拦截事件,并消耗;如果是子View需要处理,就通过某种手段让父View不拦截事件,而让事件顺利传递给子View,并让子View进行消耗处理。


基于上述的宗旨,有两种解决滑动冲突的方式:

  • 外层直接拦截法
  • 内层阻止拦截法

外层直接拦截法


所谓外层直接拦截法,就是指:由于事件都是先经过父View,再到子View,因此,如果父View需要处理当前事件,就拦截下来,自己消耗,中止分发到子View;如果父View不需要处理当前事件,则不进行拦截,向下传递给子View即可。

外层直接拦截法需要父View重写onInterceptTouchEvent方法,然后在里面仅拦截父View自己想要的事件即可。

下面给出外层拦截法的伪代码:

public boolean onInterceptTouchEvent(MotionEvent event) {
   
    boolean intercepted = false;
    
    switch (event.getAction) {
   
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if (View需要消耗当前事件) {
   
                intercepted = true;
            } else {
   
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
        default:
            break;
    }
    
    return intercepted;
}

上面伪代码简单分析一下:

(1)对于ACTION_DOWN事件,父View不可拦截,即必须返回false。因为一旦父View拦截了ACTION_DOWN事件,那么同一个事件序列中的后续所有事件都不会传递给子View了,具体原因可以在笔者的View事件分发机制系列文章的源码解读篇可了解到。

(2)对于ACTION_MOVE事件,父View则可以根据需要来决定是否拦截,需要就返回true,不需要就返回false。

(3)对于ACTION_UP事件,父View也不可拦截,即必须返回false。一是因为ACTION_UP作为事件序列的最后一个事件,其代表着本次事件生命周期的结束,我们一般会在ACTION_UP事件中处理一下状态恢复等操作,而ACTION_UP也一定会先经过父View,父View拦截下来,没有什么意义;二是因为,如果父View拦截了ACTION_UP事件,子View就接收不到了,因此子View不会响应点击事件,即onClick不会被回调,这点也是可以在笔者的View事件分发机制系列文章的源码解读篇了解得知。


内层阻止拦截法


所谓的内层阻止拦截法,则多是针对本该由子View处理的事件,却被父View拦截了的场景。它处理思路是:让父View不拦截任何事件,让所有事件都传递到子View,如果子View需要此事件,子View就消耗掉,否则交给父View处理。这种操作需要调用requestDisallowInterceptTouchEvent方法,通过这个方法来阻止父View拦截事件。

内部阻止拦截法,有两种场景:

(1)手势是在子View上,并且事件都由子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yeqiu1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值