一、常见滚定组件
Flutter提供了多种滚动组件,可以用于处理各种滚动效果。
Sliver的子组件都能滚动,但并不是所有能滚动的组件都是Sliver子组件。比如,ListView和Grid就不是Sliver子组件。
重要说3遍
ListView和Grid就 不是 Sliver子组件。
ListView和Grid就 不是 Sliver子组件。
ListView和Grid就 不是 Sliver子组件。
在 Flutter 中,可滚动组件通常由三个角色组成
-
Scrollable
:一个可滚动组件的基类,它定义了一个可滚动组件所需要的基本行为和接口。比如ListView和GridView -
Viewport
:表示一个视口,它用来显示可滚动组件中的内容。Viewport 可以是一个矩形,也可以是任意形状,它负责将滚动区域中的内容渲染到屏幕上。 -
Sliver
:一个 Scrollable 的子组件,它是用来描述可滚动区域中的一段可滚动内容的。Sliver 可以是一个矩形,也可以是任意形状,它可以包含多个子组件,例如 SliverList、SliverGrid 等。
Sliver的子组件及其作用
Sliver的子组件都能滚动
Sliver子组件 | 描述 |
---|---|
SliverAppBar |
可折叠的应用栏,随着滚动进行展开和收起 |
SliverList |
垂直的线性列表,用于显示动态数量的列表项 |
SliverGrid |
二维网格布局,可在水平和垂直方向上滚动 |
SliverToBoxAdapter |
将普通的非Sliver组件包装为Sliver组件,用于在Sliver布局中使用 |
SliverFixedExtentList | 与SliverList类似,但所有列表项的高度都是固定的,可提高性能 |
SliverPersistentHeader | 创建一个持久化的Header,始终可见,并可包含其他子组件 |
SliverPadding | 为子组件提供填充,控制子组件与边界之间的间距 |
SliverOpacity | 设置子组件的透明度,可根据滚动位置或其他条件调整子组件的显示效果 |
SliverAnimatedList | 动态、带动画效果的列表,用于在滚动视图中显示数据的变化 |
SliverAnimatedOpacity | 根据滚动位置或其他条件,以动画的方式调整子组件的透明度 |
二、Flutter 中的 Sliver
在 Flutter 中,Sliver
是一种特殊的 Widget,它可以用于创建可滚动的、高性能的列表或网格。相比于普通的列表或网格,使用 Sliver
可以提高滑动性能,减少内存占用,并且可以支持更多的交互效果。
Sliver 的基本概念
在 Flutter 中,Sliver
是指一种可以滚动的可视区域,它可以有多个子节点,每个子节点可以是一个 Widget 或者一个 LayoutBuilder。根据子节点的类型和滚动方向的不同,可以将 Sliver
分为以下几种类型:
SliverAppBar
:一个可以随着滚动渐变、折叠、固定在顶部或底部的 AppBar。SliverList
:一个垂直方向的可滚动列表。SliverGrid
:一个网格布局的可滚动列表。SliverToBoxAdapter
:一个包含单个子节点的Sliver
,可以用于将一个普通的 Widget 包装成一个可滚动的 Widget。SliverFillRemaining
:一个占满剩余空间的Sliver
,通常用于在CustomScrollView
中填充屏幕剩余的空间。
使用 Sliver 创建可滚动列表
在 Flutter 中,创建一个可滚动的列表通常需要使用 ListView
或 GridView
。但是这些 Widget 的性能并不总是最优,尤其是在列表项较多时。相比之下,使用 Sliver
可以更好地控制列表项的渲染和排布,从而提高性能。
创建 SliverList
SliverList
是一个用于显示可滚动列表的 Sliver
组件,它可以高效地渲染大量的列表项,并且可以和其他 Sliver
组件一起使用,构建复杂的可滚动布局。与普通的列表组件不同,SliverList
不会提前将所有列表项都渲染出来,而是在滚动时动态地渲染当前可见的部分,从而节省内存和渲染时间。
下面是一个简单的例子,演示如何使用 SliverList
显示一个包含 50 个列表项的可滚动列表:
dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverList Demo',
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
print('Building item $index'); // 打印日志
return ListTile(
title: Text('Item $index'),
leading: CircleAvatar(child: Text('$index')),
);
},
childCount: 50,
),
),
],
),
),
);
}
}
在这个例子中,我们创建了一个 CustomScrollView
,其中包含了一个 SliverList
。SliverList
使用 SliverChildBuilderDelegate
来构建列表项,它会根据 childCount
属性的值来确定列表项的数量。在 SliverChildBuilderDelegate
中,我们可以使用 BuildContext
和 index
参数来构建每个列表项。在这个例子中,我们为每个列表项添加了一个圆形图标,以及一个显示编号的文本。当我们向上或向下滚动列表时,SliverList
会动态地渲染当前可见的列表项,从而保证了滚动的流畅性和性能。
注意看日志的打印
创建 SliverGrid
SliverGrid
是一个用于显示网格布局的 Sliver
组件,它可以高效地渲染大量的网格项,并且可以和其他 Sliver
组件一起使用,构建复杂的可滚动布局。与普通的网格布局组件不同,SliverGrid
不会提前将所有网格项都渲染出来,而是在滚动时动态地渲染当前可见的部分,从而节省内存和渲染时间。
下面是一个例子,演示如何使用 SliverGrid
显示一个包含 50 个格子的网格布局:
dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverGrid Demo',
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
// 创建一个包含 50 个格子的 SliverGrid
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 指定每行包含 3 个格子
crossAxisCount: 3,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// 构建格子
return Container(
color: Colors.blue[100 * (index % 9 + 1)],
alignment: Alignment.center,
child: Text('Grid $index'),
);
},
childCount: 50, // 格子数量
),
),
],
),
),
);
}
}
三、Sliver 的高级用法
除了基本的 Sliver
类型外,Flutter 还提供了一些高级的 Sliver
类型,例如 SliverPersistentHeader
,SliverFillViewport
和 SliverOverlapInjector
。
三.1、 使用 SliverPersistentHeader 创建自定义 Header
SliverPersistentHeader
是一个可以自定义的 Header,它可以随着滚动渐变、折叠、固定在顶部或底部,并且可以包含任意的子节点。
与普通的头部或底部组件不同,SliverPersistentHeader
可以随着滚动而动态地改变自身的高度,从而实现更加灵活的布局效果。
下面是一个例子,演示如何使用 SliverPersistentHeader
实现一个固定在页面顶部的头部组件:
less
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverPersistentHeader Demo',
home: Scaffold(
appBar: AppBar(
title: Text('SliverPersistentHeader Demo'),
),
body: CustomScrollView(
slivers: <Widget>[
// 创建一个固定在页面顶部的 SliverPersistentHeader
SliverPersistentHeader(
pinned: true, // 固定在页面顶部
delegate: _MyHeaderDelegate(),
),
// 添加一个普通的 SliverList
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
leading: CircleAvatar(child: Text('$index')),
);
},
childCount: 50,
),
),
],
),
),
);
}
}
// 定义一个 SliverPersistentHeaderDelegate
class _MyHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
// 获取状态栏高度
final double statusBarHeight = MediaQuery.of(context).padding.top;
// 构建头部组件
return Container(
padding: EdgeInsets.only(top: statusBarHeight),
color: Colors.blue,
alignment: Alignment.center,
child: Text('Header'),
);
}
@override
double get maxExtent => 100.0; // 最大高度
@override
double get minExtent => 50.0; // 最小高度
@override
bool shouldRebuild(_MyHeaderDelegate oldDelegate) {
return false; // 不需要重新构建
}
}
在这个例子中,我们创建了一个 CustomScrollView
,其中包含了一个固定在页面顶部的 SliverPersistentHeader
。我们使用 pinned
属性将头部组件固定在页面顶部,并使用 _MyHeaderDelegate
类来构建头部组件。在 _MyHeaderDelegate
中,我们实现了 build
、maxExtent
、minExtent
和 shouldRebuild
四个方法,分别用于构建头部组件、指定最大和最小高度以及控制是否需要重新构建头部组件。在这个例子中,我们只是简单地为头部组件添加了一个背景颜色和一个文本。当我们向上或向下滚动列表时,头部组件会随着滚动而动态地改变自身的高度,从而实现了更加灵活的布局效果
不够吗?再来一个例子
dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverPersistentHeader Demo',
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
// 创建一个固定在页面顶部的 SliverPersistentHeader
SliverPersistentHeader(
pinned: true, // 固定在页面顶部
delegate: _MyHeaderDelegate(),
),
// 添加一个普通的 SliverList
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
leading: CircleAvatar(child: Text('$index')),
);
},
childCount: 50,
),
),
],
),
),
);
}
}
// 定义一个 SliverPersistentHeaderDelegate
class _MyHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
// 计算当前 Header 的高度
double height = maxExtent - shrinkOffset;
if (height < minExtent) {
height = minExtent;
}
// 计算当前 Header 的背景颜色
final double alpha = (maxExtent - height) / (maxExtent - minExtent);
final Color backgroundColor = Colors.blue.withOpacity(alpha);
// 构建 Header
return Stack(
fit: StackFit.expand,
children: [
// 背景图片
Image.network(
'https://picsum.photos/id/1/800/600',
fit: BoxFit.cover,
),
// 渐变遮罩层
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
backgroundColor.withOpacity(0.5),
backgroundColor,
],
),
),
),
// 折叠的内容
Positioned(
top: 100 - shrinkOffset,
left: 16.0,
right: 16.0,
child: Opacity(
opacity: 1 - alpha,
child: Text(
'Header',
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold, color: Colors.white),
),
),
),
],
);
}
@override
double get maxExtent => 200.0; // 最大高度
@override
double get minExtent => 50.0; // 最小高度
@override
bool shouldRebuild(_MyHeaderDelegate oldDelegate) {
return false; // 不需要重新构建
}
}
SliverPersistentHeader 对比 NestedScrollView
其实,NestedScrollView 也能实现类似的功能。
选择使用 SliverPersistentHeader
还是 NestedScrollView
取决于你的实际需求。下面是一些参考因素:
- 如果你只需要一个简单的固定在顶部的 Header,那么使用
SliverPersistentHeader
可能更加简单明了。 - 如果你需要在 Header 和内容之间添加一些复杂的交互逻辑,比如下拉刷新、上拉加载、折叠、渐变等效果,那么使用
NestedScrollView
可能更加灵活方便。 - 如果你需要在页面中嵌套多个滚动组件,并且需要让它们进行联动,那么使用
NestedScrollView
是必要的选择。
总的来说,SliverPersistentHeader
和 NestedScrollView
都是非常强大和灵活的 Flutter 组件,可以帮助开发者实现各种复杂的布局效果。你可以根据自己的实际需求来选择使用哪一个。
三.2、 使用 SliverFillViewport 创建全屏的可滚动区域
SliverFillViewport
是 Flutter 中一个重要的 Widget,它的作用是使其子元素填充视口(也就是屏幕可见的部分)。
这个 Widget 最常见的用途是在 PageView
或者 CustomScrollView
里使用,用来创建用户可以左右滑动查看的各种 "pages" 或者 "cards"。
SliverFillViewport
的主要特点是它可以让其子元素以特定的方式来填充滚动视图。举例来说,如果你希望在用户滚动视图时,每个元素都可以占据滚动视图的整个视口,那么 SliverFillViewport
就是一个很好的选择。
简单的例子的例子
less
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverFillViewport 示例',
home: Scaffold(
appBar: AppBar(title: Text('SliverFillViewport 示例')),
body: Container(
width: 300,
height: 400,
color: Colors.grey,
child: CustomScrollView(
slivers: <Widget>[
SliverFillViewport(
viewportFraction: 0.2, // 子组件高度占满视口的比例
delegate: SliverChildListDelegate(
[
Container(color: Colors.blue),
Container(color: Colors.green),
Container(color: Colors.yellow),
],
),
),
],
),
),
),
);
}
}
在这个示例中,我们将 viewportFraction 属性设置为 0.2,表示子组件的高度占视口高度的 20%。
运行该代码,你将看到在 300x400 的容器内,子组件的高度只占据了视口高度的 20%,而其余空间则留白。
这个示例演示了将 viewportFraction 属性设置为不同值时,子组件占用视口高度的比例发生变化的效果。
再来一个例子
dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
// 定义旅游景点类
class TouristAttraction {
final String name; // 名称
final String description; // 描述
final Color color; // 背景色
TouristAttraction({required this.name, required this.description, required this.color});
}
class MyApp extends StatelessWidget {
// 旅游景点列表
final List<TouristAttraction> attractions = [
TouristAttraction(
name: '埃菲尔铁塔',
description:
'埃菲尔铁塔是法国巴黎的一座铁塔,位于马斯菲尔德公园(Champ de Mars)内,是巴黎地标之一。铁塔的设计者是古斯塔夫·埃菲尔,铁塔的建造是为了纪念法国大革命一百周年。',
color: Colors.lightBlue, // 设置该景点的颜色
),
TouristAttraction(
name: '自由女神像',
description:
'自由女神像是位于美国纽约港的一座巨型铜像,是纽约市的象征之一,也是美国和法国友谊的象征。这座雕像是由法国雕塑家弗雷德里克·奥古斯特·巴托尔迪设计并制作,1886年10月28日揭幕。',
color: Colors.pink, // 设置该景点的颜色
),
TouristAttraction(
name: '泰姬陵',
description:
'泰姬陵是位于印度北部城市阿格拉的一座白色大理石陵墓,于17世纪由莫卧儿帝国皇帝沙贾汗为其逝去的爱妃慕塔芝·马哈尔而建造。泰姬陵被认为是世界上最美的建筑之一。',
color: Colors.orange, // 设置该景点的颜色
),
TouristAttraction(
name: '长城',
description:
'长城是一道蜿蜒于中国北部的防御工事,由石头、砖头、土坯等材料砌成,是中国古代的一项伟大工程。长城的修建始于公元前7世纪,历经2000多年的修建和扩建,成为了世界上最长的城墙。',
color: Colors.yellow, // 设置该景点的颜色
),
TouristAttraction(
name: '比萨斜塔',
description:
'比萨斜塔是意大利比萨市的一座独立的钟楼,以其明显的倾斜而闻名于世。斜塔的建造始于12世纪,由于斜塔的基础建设不够坚固,导致了斜塔的倾斜。',
color: Colors.green, // 设置该景点的颜色
),
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverFillViewport 演示',
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('旅游景点'),
floating: true,
),
SliverFillViewport(
delegate: SliverChildBuilderDelegate(
// 构建子元素
(BuildContext context, int index) {
final attraction = attractions[index];
return Container(
padding: EdgeInsets.all(16.0),
color: attraction.color, // 设置背景色
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
attraction.name,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 8),
Text(
attraction.description,
style: TextStyle(
fontSize: 16,
color: Colors.white,
),
),
],
),
);
},
childCount: attractions.length,
),
),
],
),
),
);
}
}
在这个例子中,我们创建了一个 TouristAttraction
类,用于表示旅游景点的名称和描述。我们将多个景点添加到 attractions
列表中,并在 CustomScrollView
中使用 SliverFillViewport
来展示它们的介绍。在 SliverFillViewport
中,我们使用 SliverChildBuilderDelegate
来构建子元素,并通过 childCount
属性指定子元素的数量。在 SliverChildBuilderDelegate
的回调函数中,我们遍历 attractions
列表,并根据每个景点的名称和描述创建一个子元素。由于 SliverFillViewport
会铺满整个 Viewport
,因此所有的子元素都会占用整个屏幕。最后,我们将 CustomScrollView
放到 Scaffold
的 body
中,并在 SliverAppBar
中设置标题和浮动属性。
三.3、 使用 SliverOverlapInjector 实现重叠效果
SliverOverlapInjector
是一个特殊的 Sliver Widget,用于在两个 Sliver 之间插入一个非滚动的 Widget,这个 Widget 可以覆盖在上一个 Sliver 的底部,同时也可以被下一个 Sliver 的内容覆盖。这个 Widget 主要用于解决两个 Sliver 之间的重叠问题。
最常见的使用场景是在 Flutter 的自定义滚动视图(
CustomScrollView)中,通常在具有弹性头部(
SliverAppBar)的列表上方添加额外的内容,这些内容将在列表滚动时滑入视图并在列表内容滚动到顶部时停止。
以下是一个简单的示例,用于演示如何使用 SliverOverlapInjector
,我已添加详细的中文注释以帮助理解:
dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverOverlapInjector 示例',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: "SliverOverlapInjector 示例"),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<NestedScrollViewState> _key = GlobalKey<NestedScrollViewState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
key: _key,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text(widget.title),
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://picsum.photos/400/200', fit: BoxFit.cover),
),
),
];
},
body: Builder(
builder: (BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('列表项 #$index'),
);
},
childCount: 50,
),
),
),
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
],
);
},
),
),
);
}
}
在这个示例中,SliverAppBar
提供了一个可以伸展的 AppBar,SliverOverlapAbsorber
吸收了其中的重叠部分,并通过一个 NestedScrollView.sliverOverlapAbsorberHandleFor
共享给 SliverOverlapInjector
。这样,当我们在 CustomScrollView
中滚动时,SliverAppBar
下方的列表会根据滚动的情况逐渐滑入视图,形成重叠效果。
总结
在 Flutter 中,`Sliver以上是关于使用 Sliver 构建可滚动区域的基础介绍和示例代码,希望能够帮助你更好地理解和使用 Sliver 相关的 Widget。使用 Sliver 可以方便地构建出各种复杂的可滚动效果,同时也可以通过 SliverOverlapInjector 实现重叠效果,为用户提供更加丰富的交互体验。如果你有任何问题或疑问,欢迎继续提问。
可滚动布局模型和 Sliver布局模型 是两个东西吗
可滚动布局模型
和Sliver布局模型
都是Flutter中用于实现可滚动性布局的布局模型,但它们的实现方式和使用方法略有不同。
可滚动布局模型
(例如ListView、GridView、SingleChildScrollView等)是基于Scrollable类实现的,它们使用Viewport来显示子组件,并支持滚动、滑动和惯性等手势操作。这些组件通常使用较少的代码来实现常见的可滚动性布局模式。
Sliver布局
模型则更为灵活,它的核心是使用Sliver来构建可滚动的子组件,可以自由组合和嵌套多种滚动组件以实现复杂的滚动效果。Sliver布局模型通常需要更多的布局代码和布局知识,但可以实现更高级和自定义的滚动效果。
可以将Sliver布局模型看作是可滚动布局模型的扩展,它提供了更多的自定义选项和更高级的滚动效果,例如可扩展的应用栏、流畅的滚动列表、复杂的网格布局等等。
总之,可滚动布局模型和Sliver布局模型都是Flutter中用于实现可滚动性布局的布局模型,开发者可以根据需要选择合适的布局模型来实现所需的滚动效果。