前言
首先呢,为什么会有PageView嵌套PageView这个需求……
我们来看下抖音的交互:
抖音交互
从图上不难分析出,首页里面放了2个tab,右边的菜单栏则是独立存在的一个页面
再加上嵌套滑动,所以实现方式就是,PageView里面再嵌套一个布局,首页那块无法就是在这个嵌套布局中加入一个TabBarView就好了嘛,菜单栏用状态管理来更新菜单内容,so easy~
然而事实证明我还是太年轻了……TabBarView其实就是PageView的拓展实现,然鹅,PageView是不支持嵌套滑动的……
解决方案
手势监听,给所有View加上动画,当划出菜单的时候,用动画方式移动底部的标签栏
NestedScrollerView 加上PageView 的physics?
实现一个能支持嵌套滑动的PageView
当然,如果我采用第一种方案也就没这篇文章了,其中方案调研的血泪史不提也罢。
首先需要了解的知识(课前准备)
Flutter中,提到嵌套滑动,自然第一想到的就是NestedScrollView,所以如果想给PageView加上嵌套滑动机制,学习下NestedScrollView及其核心原理能给我们很大的帮助。
Scrollable.dart 中的ScrollerController,ScrollerPosition等,了解其用途、基本含义和使用方法(当然,这篇文章需要的没那么多,要想理解,只需要看下ScrollerController、ScrollerPosition即可,那些beginActivity、ScrollActivity什么的不明白也罢,这篇文章用不到,用ScrollPositionWithSingleContext复制黏贴过来的就行)。
万里长征第一步,让它嵌套滑动起来
做好课前准备之后,我们最简单的描述一下 NestedScrollView 的嵌套滑动步骤:
创建_NestedScrollCoordinator ,同时创建2个ScrollController,用于管理整体的CunstomScrollerView和内部primary的可滑动布局
通过 自定义的ScrollController ,返回自定义ScrollPosition,顺便将Delegate 具体实现交给 _NestedScrollCoordinator来实现。
delegate 通过具体的 applyUserOffset 方法来控制整个列表的内容,顺序是往上或者往左滑先滑外部,往下或往右滑先处理内部。
如果实现PageView的嵌套滑动,也可以采取这个思路。
一些小小坑
PageView本身是不支持primary的,所以如果想像NestedScrollerView那样不需要给child传入特定controller,直接用即可的话,就需要实现一个支持Primary的PageView;
PageView是不会保活的,所以如果拉到主PageView的第二页,包含子PageView的第一页就会dispose,因此丢失滑动状态,再拉回来的时候自然展示的是第一页,而不是嵌套滑动之后的最大页。所以这块需要保活处理一下下
核心代码
1class _ChildPagePosition extends ScrollPosition
2 implements PageMetrics, ScrollActivityDelegate{
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
后记
当然,支持嵌套滑动仅仅只是开始……
如图所示,目前仅仅嵌套滑动了而已,松开手之后的physics效果,拉到一半再拉回去等操作也没特殊处理,嘛,不过这是以后的事了……