无侵入式的解决 ViewPager2 跟横向滑动子 View 手势冲突的一种思路

前言

在最近的开发中,我恰好遇到一个典型又棘手的问题 —— ViewPager2 与横向滚动 View(如 HorizontalScrollView 或 ReactHorizontalScrollView)之间的手势冲突

网上关于 ViewPager2 滑动冲突的文章一搜一大把,但不少内容已过时,且大同小异,真正符合实际需求的少之又少。

因此,这里我把自己的解决思路与实践过程做个记录,希望能为有类似困扰的大佬们提供点参考。


ViewPager2 滑动冲突

页面结构

先来看一下页面的基本结构:

在这里插入图片描述

  • 最外层是一个 Activity,其中包含了 ViewPager2、底部的 Tab 和顶部的 TabLayout。
  • ViewPager2 中承载着多个 Fragment,其中第一个是原生页面,其余则是加载的 RN(React Native)页面。
  • 每个 Fragment 中都有可能存在横向可滑的组件,例如 HorizontalScrollView 或ReactHorizontalScrollView。

手势冲突的表现

在未做任何手势处理的情况下,主要问题表现如下:

  • 正常情况:当手指在页面的非横向滑动区域左右滑动(无论是 Native 页还是 RN 页),事件会正确地传递给 ViewPager2,切换页面一切正常 —— 符合预期。
  • Native 页问题:当滑动落点在 HorizontalScrollView 上时,手势响应行为就变得不可预期。可能会触发 ViewPager2 的滑动,也可能响应 HorizontalScrollView,具体取决于手指按压的时长和滑动速度,这种不确定性就不是我所预期的行为。
  • RN 页问题:如果落点在 ReactHorizontalScrollView 上,则一定是响应自己的滑动事件,ViewPager2 的滑动彻底被拦截。在手势优先级上这个是符合我的预期的,但当 ReactHorizontalScrollView 内容较少、无法实际左右横向滚动时,滑动操作会没有反应,给人一种页面卡顿或者滑动失效的感觉。

我的预期

我希望手势响应遵循以下逻辑:

  1. 如果 子 view 是可横向滑动的 view,优先响应子 View 的滑动事件

  2. 如果子 View 无法左右滑动,再将事件交由 ViewPager2 处理,去响应 ViewPager2 的滑动事件,避免给用户一种假死的错觉


问题梳理以及解决思路

问题梳理

结合上文的现象与项目背景,当前我们面临的关键问题可以归纳为:
1. 滑动横向子 View 时,事件消费行为不确定,有可能是 ViewPager2 响应,有可能是子 view 响应。
2. 当子 View 本身无法横向滚动时,希望将滑动事件交由 ViewPager2 处理,避免“假死”状态。
3. RN 侧的代码不可修改,问题需在 Native 层解决,因此需要一种不修改其它代码的通用的方式来处理所有横向滚动 View 的冲突问题。

解决方案

这一方案其实经历了多次迭代优化,期间尝试过多种方式,但或多或少都存在边界问题。以下是最终实现,已在多种场景下验证,效果稳定,满足预期。

先来一个个的分析下上面问题的核心原因:

滑动横向子 View 时,事件消费行为不确定,有可能是 ViewPager2 响应,有可能是子 view 响应

原因:
当手指触摸到HorizontalScrollView的那一刻,根据手指按下的时长和滑动速度,会决定ViewPager2HorizontalScrollView 到底谁来响应手势。

如果手指按下停顿一下,HorizontalScrollView 大概率能收到 ACTION_DOWN事件,然后后续的 ACTION_MOVE 事件也是由HorizontalScrollView来响应。

但是如果手指按下后立刻就滑动,此时,HorizontalScrollView就大概率收不到ACTION_DOWN事件了,被 ViewPager2 给拦截并消费掉了,此时,滑动事件也是由 ViewPager2 响应的。
这就导致了滑动时可能是宫格内部的滑动,也可能会触发 ViewPaiger2 的切换。

解决
核心要解决的就是当手指触摸的是可横向滚动的子 view 时,要确保子 view 来消费事件,而不是ViewPager2

但是由于 ViewPager2 是相对上层的父 view,因此,事件一定是先到 ViewPager2 的,如果 ViewPager2 没有消费事件,子 view 才有机会响应事件。

在这里插入图片描述

查看源码发现 ViewPage2 本质上就是个 RecyclerView,那么可以在初始化 ViewPager2 时,给 内部的 RecyclerView 加个addOnItemTouchListener ,在 ACTION_DOWN 的时候去找一下当前手指是否是触摸在可横向滚动的 View 上,如果是,则让 子 view 获取焦点,并且通过调用 requestDisallowInterceptTouchEventViewPager2 不要拦截事件,确保子 View 能响应 ACTION_DOWN

示例代码:

    fun handleTouchEvent(viewPager2: ViewPager2) {
   
   
        this.viewPager = viewPager2
        recyclerView = viewPager2.getChildAt(<
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

XeonYu

码字不易,鼓励随意。

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

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

打赏作者

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

抵扣说明:

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

余额充值