跳到主要内容

五、滚动组件

本页的属于低级组件,使用频率并不那么高,详细的内容还是继续看随书介绍:6.1 可滚动组件简介 | 《Flutter实战·第二版》

Sliver布局模型

Sliver布局模型在Flutter中用于创建高效的可滚动列表。"Sliver"一词来自于"slice"(切片),意味着屏幕上的一小部分,这些小部分可以组合成一个滚动视图。在Flutter中,Slivers是构建复杂滚动效果(如自定义的滚动动画、粘性头部、懒加载列表等)的核心。

只有当 Sliver 出现在视口中时才会去构建它,这种模型也称为“基于Sliver的列表按需加载模型”。可滚动组件中有很多都支持基于Sliver的按需加载模型,如ListViewGridView,但是也有不支持该模型的,如SingleChildScrollView

Flutter 中的可滚动组件主要由三个角色组成:Scrollable、Viewport 和 Sliver:

  • Scrollable :用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport 。
  • Viewport:显示的视窗,即列表的可视区域;
  • Sliver:视窗里显示的元素。

Scrollable 是一个低级别的可滚动的widget,它直接与滚动事件和视口(viewport)交互。它是许多高级滚动widget的基础,比如 ListViewGridView 等。Scrollable 通常与 ScrollControllerViewport 一起使用,创建自定义的滚动效果和视图。

具体布局过程:

  1. Scrollable 监听到用户滑动行为后,根据最新的滑动偏移构建 Viewport 。
  2. Viewport 将当前视口信息和配置信息通过 SliverConstraints 传递给 Sliver。
  3. 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,
),
),
],
);
},
),
),
));
}