[Flutter]从零开始实现一个嵌套滑动的PageView(一)

6,105

目录

  1. [Flutter]从零开始实现一个嵌套滑动的PageView(一)
  2. [Flutter]从零开始实现一个嵌套滑动的PageView(二)
  3. [Flutter]从零开始实现一个嵌套滑动的PageView(三)

前言

首先呢,为什么会有PageView嵌套PageView这个需求……

我们来看下抖音的交互:

抖音交互
抖音交互

从图上不难分析出,首页里面放了2个tab,右边的菜单栏则是独立存在的一个页面

再加上嵌套滑动,所以实现方式就是,PageView里面再嵌套一个布局,首页那块无法就是在这个嵌套布局中加入一个TabBarView就好了嘛,菜单栏用状态管理来更新菜单内容,so easy~

然而事实证明我还是太年轻了……TabBarView其实就是PageView的拓展实现,然鹅,PageView是不支持嵌套滑动的……

解决方案

  1. 手势监听,给所有View加上动画,当划出菜单的时候,用动画方式移动底部的标签栏

  2. NestedScrollerView 加上PageView 的physics?

  3. 实现一个能支持嵌套滑动的PageView

当然,如果我采用第一种方案也就没这篇文章了,其中方案调研的血泪史不提也罢。

首先需要了解的知识(课前准备)

  1. Flutter中,提到嵌套滑动,自然第一想到的就是NestedScrollView,所以如果想给PageView加上嵌套滑动机制,学习下NestedScrollView及其核心原理能给我们很大的帮助。

  2. Scrollable.dart 中的ScrollerController,ScrollerPosition等,了解其用途、基本含义和使用方法(当然,这篇文章需要的没那么多,要想理解,只需要看下ScrollerController、ScrollerPosition即可,那些beginActivity、ScrollActivity什么的不明白也罢,这篇文章用不到,用ScrollPositionWithSingleContext复制黏贴过来的就行)。

万里长征第一步,让它嵌套滑动起来

做好课前准备之后,我们最简单的描述一下 NestedScrollView 的嵌套滑动步骤:

  1. 创建_NestedScrollCoordinator ,同时创建2个ScrollController,用于管理整体的CunstomScrollerView和内部primary的可滑动布局

  2. 通过 自定义的ScrollController ,返回自定义ScrollPosition,顺便将Delegate 具体实现交给 _NestedScrollCoordinator来实现。

  3. delegate 通过具体的 applyUserOffset 方法来控制整个列表的内容,顺序是往上或者往左滑先滑外部,往下或往右滑先处理内部。

如果实现PageView的嵌套滑动,也可以采取这个思路。

一些小小坑

  1. PageView本身是不支持primary的,所以如果想像NestedScrollerView那样不需要给child传入特定controller,直接用即可的话,就需要实现一个支持Primary的PageView;

  2. PageView是不会保活的,所以如果拉到主PageView的第二页,包含子PageView的第一页就会dispose,因此丢失滑动状态,再拉回来的时候自然展示的是第一页,而不是嵌套滑动之后的最大页。所以这块需要保活处理一下下

核心代码

 1class _ChildPagePosition extends ScrollPosition
2    implements PageMetricsScrollActivityDelegate 
{
3  _ChildPagePosition({
4    this.parentController,
5    ScrollPhysics physics,
6    ScrollContext context,
7    this.initialPage = 0,
8    bool keepPage = true,
9    double viewportFraction = 1.0,
10    double initialPixels = 0.0,
11    ScrollPosition oldPosition,
12  })
13      : assert(initialPage != null),
14        assert(keepPage != null),
15        assert(viewportFraction != null),
16        assert(viewportFraction > 0.0),
17        _viewportFraction = viewportFraction,
18        _pageToUseOnStartup = initialPage.toDouble(),
19        super(
20        physics: physics,
21        context: context,
22        keepScrollOffset: keepPage,
23        oldPosition: oldPosition,
24      ) {
25    // If oldPosition is not null, the superclass will first call absorb(),
26    // which may set _pixels and _activity.
27    if (pixels == null && initialPixels != null)
28      correctPixels(initialPixels);
29    if (activity == null)
30      goIdle();
31    assert(activity != null);
32  }
33
34  /// 中间一大堆无关方法略过
35
36  @override
37  void applyUserOffset(double delta) {
38    updateUserScrollDirection(
39        delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
40    final double newPixels = pixels -
41        physics.applyPhysicsToUserOffset(this, delta);
42    final double overScroll = physics.applyBoundaryConditions(this, newPixels);
43    if (overScroll == 0) {
44      setPixels(newPixels);
45    } else {
46      if(parentController!=null){
47        if(parentController.position is _PagePosition){
48          (parentController.position as _PagePosition).applyClampedDragUpdate(-overScroll);
49        }
50      }
51      print("触发上级滑动");
52    }
53  }
54}

Look Look 效果

NestedPageView 1.0
NestedPageView 1.0

后记

当然,支持嵌套滑动仅仅只是开始……

如图所示,目前仅仅嵌套滑动了而已,松开手之后的physics效果,拉到一半再拉回去等操作也没特殊处理,嘛,不过这是以后的事了……