开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情
SingleChildScrollView
SingleChildScrollView类似于iOS中的UIScrollView,它只接收一个子组件,定义如下:
SingleChildScrollView({
this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
this.reverse = false,
this.padding,
bool primary,
this.physics,
this.controller,
this.child,
})
除了可滚动组件的通用属性外,重点关注primary属性:它表示是否使用widget树中默认的PrimaryScrollController(MaterialApp组件树中已经默认包含一个PrimaryScrollController); 当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且没有指定controller时,primary默认为true。
需要注意的是,通常SingleChildScrollView只应在期望的内容不会超过屏幕太多时使用,因为SingleChildScrollView不支持Sliver的延迟加载模型,也就是和iOS的UIScrollView一样,在初始化的同时,会一次性加载其子组件。所以如果预计视图窗口可能包含超出屏幕尺寸太多内容时,那么使用SingleChildScrollView将会非常昂贵(性能差),此时应该使用一些支持Sliver延迟加载的可滚动组件,比如ListView或者GirdView。
实例
class SingleChildScrollViewTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return Scrollbar( // 显示进度条
child: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Center(
child: Column(
//动态创建一个List<Widget>
children: str.split("")
//每一个字母都用一个Text显示,字体为原来的两倍
.map((c) => Text(c, textScaleFactor: 2.0,))
.toList(),
),
),
),
);
}
}
PageView
要实现页面切换和Tab布局,可以使用PageView组件。在大多数App都包含Tab换页效果、图片轮动以及抖音上下滑页切换视频功能等等,都可以通过PageView轻松实现。
PageView({
Key? key,
this.scrollDirection = Axis.horizontal, // 滑动方向
this.reverse = false,
PageController? controller,
this.physics,
List<Widget> children = const <Widget>[],
this.onPageChanged,
//每次滑动是否强制切换整个页面,如果为false,则会根据实际的滑动距离显示页面
this.pageSnapping = true,
//主要是配合辅助功能用的,后面解释
this.allowImplicitScrolling = false,
//后面解释
this.padEnds = true,
})
// Tab 页面
class Page extends StatefulWidget {
const Page({
Key? key,
required this.text
}) : super(key: key);
final String text;
@override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
@override
Widget build(BuildContext context) {
print("build ${widget.text}");
return Center(child: Text("${widget.text}", textScaleFactor: 5));
}
}
@override
Widget build(BuildContext context) {
var children = <Widget>[];
// 生成 6 个 Tab 页
for (int i = 0; i < 6; ++i) {
children.add( Page( text: '$i'));
}
return PageView(
// scrollDirection: Axis.vertical, // 滑动方向为垂直方向
children: children,
);
}
如果将PageView的滑动方向指定为垂直方向,则会变为上下滑动切换页面。
页面缓存
上面运行时,每当页面切换时会触发新Page页的build,比如我们从第一页滑到第二页,再滑回第一页时,打印如下:
flutter: build 0
flutter: build 1
flutter: build 0
可见PageView默认并没有缓存功能,一旦页面滑出屏幕它就会销毁,这和ListView/GridView不一样,在创建ListView/GridView时可以手动指定Viewport之外多大范围内的组件需要预渲染和缓存。只有当组件滑出屏幕后又滑出预渲染区域,组件才会被销毁,但是不幸的是PageView并没有cacheExtent参数。看PageView的源码:
child: Scrollable(
...
viewportBuilder: (BuildContext context, ViewportOffset position) {
return Viewport(
// TODO(dnfield): we should provide a way to set cacheExtent
// independent of implicit scrolling:
// https://github.com/flutter/flutter/issues/45632
cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0,
cacheExtentStyle: CacheExtentStyle.viewport,
...
);
},
)
发现虽然没有cacheExtent,但是在allowImplicitScrolling为true时设置了预渲染区域,此时缓存类型为CacheExtentStyle.viewport,则cacheExtent表示缓存长度是几个Viewport的宽度,cacheExtent为1.0,则代表前后各缓存一个页面宽度,即前后各一页。既然如此,那我们将PageView的allowImplicitScrolling置为true就可以缓存前后两页了,运行后,控制台打印信息如下:
flutter: build 0
flutter: build 1
滑到第二页时:
flutter: build 0
flutter: build 1
flutter: build 2
当滑回第一页时,控制台信息不变,也就是缓存成功了。