五、滚动组件
本页的属于低级组件,使用频率并不那么高,详细的内容还是继续看随书介绍:6.1 可滚动组件简介 | 《Flutter实战·第二版》
Sliver布局模型
Sliver布局模型在Flutter中用于创建高效的可滚动列表。"Sliver"一词来自于"slice"(切片),意味着屏幕上的一小部分,这些小部分可以组合成一个滚动视图。在Flutter中,Slivers是构建复杂滚动效果(如自定义的滚动动画、粘性头部、懒加载列表等)的核心。
只有当 Sliver 出现在视口中时才会去构建它,这种模型也称为“基于Sliver的列表按需加载模型”。可滚动组件中有很多都支持基于Sliver的按需加载模型,如ListView
、GridView
,但是也有不支持该模型的,如SingleChildScrollView
。
Flutter 中的可滚动组件主要由三个角色组成:Scrollable、Viewport 和 Sliver:
- Scrollable :用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport 。
- Viewport:显示的视窗,即列表的可视区域;
- Sliver:视窗里显示的元素。
Scrollable
是一个低级别的可滚动的widget,它直接与滚动事件和视口(viewport)交互。它是许多高级滚动widget的基础,比如 ListView
、GridView
等。Scrollable
通常与 ScrollController
和 Viewport
一起使用,创建自定义的滚动效果和视图。
具体布局过程:
- Scrollable 监听到用户滑动行为后,根据最新的滑动偏移构建 Viewport 。
- Viewport 将当前视口信息和配置信息通过 SliverConstraints 传递给 Sliver。
- Sliver 中对子组件(RenderBox)按需进行构建和布局,然后确认自身的位置、绘制等信息,保存在 geometry 中(一个 SliverGeometry 类型的对象)。 白色区域为设备屏幕,也是 Scrollable 、 Viewport 和 Sliver 所占用的空间,三者所占用的空间重合,父子关系为:Sliver 父组件为 Viewport,Viewport的 父组件为 Scrollable (Scrollable>Viewport>Sliver)。 注意ListView 中只有一个 Sliver,在 Sliver 中实现了子组件(列表项)的按需加载和布局。
Scrollable
用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport,我们看一下其关键的属性:
Scrollable({
...
this.axisDirection = AxisDirection.down,
this.controller,
this.physics,
required this.viewportBuilder, //后面介绍
})
在可滚动组件的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。由于可滚动组件的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理。
Viewport
Viewport({
Key? key,
this.axisDirection = AxisDirection.down,
this.crossAxisDirection,
this.anchor = 0.0,
required ViewportOffset offset, // 用户的滚动偏移
// 类型为Key,表示从什么地方开始绘制,默认是第一个元素
this.center,
this.cacheExtent, // 预渲染区域
//该参数用于配合解释cacheExtent的含义,也可以为主轴长度的乘数
this.cacheExtentStyle = CacheExtentStyle.pixel,
this.clipBehavior = Clip.hardEdge,
List<Widget> slivers = const <Widget>[], // 需要显示的 Sliver 列表
})
Sliver
Sliver 主要作用是对子组件进行构建和布局,比如 ListView 的 Sliver 需要实现子组件(列表项)按需加载功能,只有当列表项进入预渲染区域时才会去对它进行构建和布局、渲染。
示例
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Scrollable 示例'),
),
body: Scrollable(
viewportBuilder: (BuildContext context, ViewportOffset position) {
return Viewport(
offset: position,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('项 $index')),
childCount: 50,
),
),
],
);
},
),
),
));
}