Flutter 两个 PageView 嵌套滑动优化处理

264 阅读4分钟

一、吐槽现状

当看到这个标题时,大家首先想到的或许是 Flutter 手势冲突的解决,随之而来的可能是晦涩难懂的介绍和源代码堆砌。但本文却另辟蹊径,摒弃了复杂的手势竞争管理处理方式,仅通过简洁的代码便可轻松解决滑动问题。接下来,就让我们一同领略这一神奇操作!

二、页面结构

image.png

1. 概括上面的图片表达的内容

一个页面包含一个 PageView1, PageView1 又有两个视图PV1-View1Pv1-View2。 其中 PV1-View2视图包含一个 PageView2, 而这个 PageView2 也又有两个视图PV2-View1Pv2-View2

2. 我们要解决的问题 (重点)

  • 场景

    PageView1 滑动到 PV1-View2时,然后 PV1-View2 页面的 PageView2 滑动到 PV2-View1 时。

  • 问题

    在上面的场景下,当我们再次向右滑动 PV2-View1时, 页面没有自动切换到 PageView1PV1-View1 视图。

  • 期望结果

    在滑动下能够流畅的切换到 PageView1PV1-View1 的视图, 然后,再 向左 滑动进入 PV1-View2PageView2Pv2-View1 的视图。

  • 最终效果展示

    20250515181805_rec_.gif

三、解决问题的结论

实现步骤

  • 我们使用 NotificationListener 包括 PageView1 来监听 PageView1PageView2 的滑动。

  • 通过 NotificationListener 的 {bool Function(ScrollNotification)? onNotification} 方法获取超出滑动()的滑动信息 OverscrollNotification

  • 在滑动信息 OverscrollNotification 中的 depth 属性来区分是滑动的 PageView1 还是 PageView2;

  • 然后由 OverscrollNotificationoverscroll 属性判断滑动的方向 (overscroll > 0 是向左滑动;overscroll < 0 是向右滑动);

  • 再由 OverscrollNotificationvelocity 属性判断是否 向右 滑动到边缘。

注意事项

PageViewphysics 属性在不同平台上默认值如下:

  • Android:默认为ClampingScrollPhysics,这会阻止滚动超过内容范围。
  • iOS:默认为BouncingScrollPhysics,允许滚动超过并回弹。

我们需要超出内容的滑动通知,而 IOS 的默认 physics 不满足需求,所以我们要修改 PageView2physicsClampingScrollPhysics 进行两端统一。

四、涉及知识点介绍

  • NotificationListener

    在 Flutter 中,NotificationListener 是一个非常强大的小部件,用于监听和处理滚动通知(如滚动开始、滚动结束、滚动位置变化等)。它通常与滚动组件(如 ListViewGridView SingleChildScrollView 或 PageView)结合使用,以监听滚动事件并根据需要执行操作。 类代码:

    class NotificationListener<T extends Notification> extends ProxyWidget {
        /// Creates a widget that listens for notifications.
        const NotificationListener({
          super.key,
          required super.child,
          this.onNotification,
        });
    
        final NotificationListenerCallback<T>? onNotification;
    
        @override
        Element createElement() {
          return _NotificationElement<T>(this);
        }
    }
    

    NotificationListener 的使用非常简单,它需要一个 onNotification 回调函数来处理通知。

  • OverscrollNotification

    在 Flutter 中,OverscrollNotification 是一种滚动通知,用于在滚动超出边界时触发。类代码:

    class OverscrollNotification extends ScrollNotification {
       OverscrollNotification({
         required super.metrics,
         required BuildContext super.context,
         this.dragDetails,
         required this.overscroll,
         this.velocity = 0.0,
       }) : assert(overscroll.isFinite),
            assert(overscroll != 0.0);
            
       // 如果 Scrollable 因拖动而过度滚动,则有关该拖动的详细信息将更新。
       final DragUpdateDetails? dragDetails;
       
       // Scrollable 过度滚动的逻辑像素数。
       final double overscroll;
       
       // 发生过度滚动时 ScrollPosition 变化的速度。
       final double velocity;
     }
    

    上面介绍了官方解释,下面我们用白话在絮叨一遍:

    • DragUpdateDetails? dragDetails

      这个参数是当你过度的滑动时,当时的拖动信息, 默认值为 Null。 就是说你不过度拖拽就不会有该属性值的更新。

    • double overscroll

      这个属性就是你过度拖拽时 Scrollable 滚动的像素数。 这对于“开始”侧的过度滚动来说为负,而对于“结束”侧的过度滚动来说为正。

    • double velocity

      这个就是你过度拖拽时 ScrollPosition 的变化速度。(位置的变化,切记

    • int depth

      因为 OverscrollNotification 集成于 ScrollNotification , ScrollNotification 又混入 ViewportNotificationMixin , 而 depth 是混入的参数属性。该属性是通知已冒泡的视口数量,通常监听器仅响应 depth 为零的通知。大白话就是:用于区分你拖动的那个 PageView

  • ClampingScrollPhysics

    在 Flutter 中,ClampingScrollPhysics 是一种滚动物理模型,用于限制滚动范围,防止滚动超出内容的实际范围。它通常用于 PageView 和其他滚动组件,确保滚动只能在内容的范围内进行。

五、代码展示

/// PageView1 视图
NotificationListener<OverscrollNotification>(
  onNotification: (notification) {
    if (notification.depth == 1 && notification.overscroll < 0 && notification.velocity == 0) {
        _pageController.animateToPage(0, duration: Duration(milliseconds: 200), curve: Curves.linear);
        return false;
      }
    return true;
  },
  child: PageView.builder(
    controller: _pageController,
    ... // 省略无用代码
  ).expanded(),
),

/// PageView2 的视图
PageView.builder(
  physics: const ClampingScrollPhysics(),
  ...// 省略无用代码

六、总结

通过以上操作,我们就能实现 PageView 嵌套,流畅的切换了。小小的操作解决大大的问题,请为之打 Call 吧!!!