Flutter SliverAppBar 完全指南 - 第一部分
创建 Flutter 中自定义 SliverAppBar 行为的指南
目录
- Flutter SliverAppBar 完全指南 - 第一部分
引言 ℹ️
扩展应用栏(可折叠工具栏)是 Material Design 应用栏行为中广泛使用的一种,当用户向上滚动时可以隐藏应用栏内容。一个简单的用例是 AppBar,当用户向下滚动时可能会显示完整的个人资料图片,当用户向上滚动时会逐渐过渡到只显示用户名。
在 Flutter 中,我们可以使用 SliverAppBar 小部件来实现相同的行为。正如小部件名称所示,它只能在 CustomScrollView 小部件中使用。此小部件通过直接向其提供 slivers(SliverAppBar、SliverList、SliverGrid 等)来帮助您创建各种滚动效果,例如列表、网格和扩展标题。
但是等等…… sliver 到底是什么? ?
Sliver 只是一个可滚动区域的一部分。仅此而已!您使用的所有可滚动视图,如 ListView 和 GridView,实际上都是使用 Slivers 实现的。您可以将 Slivers 视为一个较低级别的接口,它在实现可滚动区域方面提供更精细级别的控制。由于 slivers 可以仅在项滚动到视图时才惰性地构建每个项,因此 slivers 特别适用于高效地滚动大量子项。- Slivers, Demystified
我们要构建什么? ?
我们将构建以下自定义行为
来发现 SliverAppBar 小部件! ? ?
SliverAppBar( pinned: _pinned, snap: _snap, floating: _floating, expandedHeight: 160.0, collapsedHeight: 80.0, flexibleSpace: const FlexibleSpaceBar( title: Text('SliverAppBar'), background: FlutterLogo(), ), ),
如前所述,此小部件直接在 CustomScrollView 中使用,并包含一些有用的属性来自定义其行为。
最重要的属性是
我们将重点关注这些属性
collapsedHeight:一个双精度值,定义了应用栏折叠时的高度。expandedHeight:一个双精度值,定义了应用栏展开时的高度。
flexibleSpace:此小部件堆叠在工具栏和标签栏的后面,通常与FlexibleSpaceBar小部件一起使用。FlexibleSpaceBar 小部件包含属性,用于在应用栏展开时自定义空间。
我们将重点关注两个属性
-
title是一个在折叠/展开应用栏时会(缩放)的小部件。 -
background是一个在应用栏折叠时会淡出的(fade out)小部件。 -
还有其他属性控制折叠应用栏的行为,如
pinned、floating、snap等。要了解更多信息,请查看 官方文档。
开始构建吧! ?
首先,我们将使用
flutter_hooks包来更轻松地管理局部状态和处理 ScrollControllers。当然,您可以使用StatefulWidget和setState或任何其他局部状态管理解决方案。让我们开始添加 Stack 小部件以添加电影背景图片和 CustomScrollView 小部件,以便能够使用 SliverAppBar。
class MovieProfilePage extends HookWidget { const MovieProfilePage({super.key, required this.movieDetails}); final MovieDetails movieDetails; @override Widget build(BuildContext context) { return Stack( children: [ /// The background image BlurredBackdropImage(movieDetails: movieDetails), CustomScrollView( slivers: [ ], ), ], ); } }
现在让我们将 SliverAppBar 添加到 CustomScrollView 中。
@override Widget build(BuildContext context) { const collapsedBarHeight = 60.0; const expandedBarHeight = 400.0; return Stack( children: [ /// Blurred Background Image Widget BlurredBackdropImage(movieDetails: movieDetails), CustomScrollView( slivers: [ SliverAppBar( expandedHeight: expandedBarHeight, collapsedHeight: collapsedBarHeight, centerTitle: false, pinned: true, /// 1 title: CollapsedAppBarContent(movieDetails: movieDetails), elevation: 0, /// 2 backgroundColor: Colors.transparent, leading: const BackButton( color: Colors.white, ), /// 3 flexibleSpace: FlexibleSpaceBar( background: ExpandedAppBarContent( movieDetails: movieDetails, ), ), ), /// 4 SliverToBoxAdapter( child: ConstrainedBox( constraints: BoxConstraints( minHeight: MediaQuery.of(context).size.height, ), child: Material( elevation: 7, borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15), ), child: PageBodyWidget(movieDetails: movieDetails), ), ), ) ], ), ], ); } }
- 添加
CollapsedAppBarContent小部件,该小部件包含顶部Row到 title 字段。 title 小部件将始终保持可见。 - 将 AppBar 的
background颜色更改为transparent以显示BlurredBackdropImage小部件。 - 添加
FlexibleSpaceBar小部件并将ExpandedAppBarContent小部件分配给 background 字段。当应用栏折叠时,background字段内容会淡出,这正是我们需要的! - 添加
SliverToBoxAdapter小部件以添加主体内容。简单来说,SliverToBoxAdapter所做的是连接 slivers 和常规小部件(即基于 box 的小部件)。由于CustomScrollView的直接子项只能是 slivers 对象。
现在,如果我们分析所需的设计,我们会发现
CollapsedAppBarContent小部件与ExpandedAppBarContent小部件重叠。此外,当应用栏折叠时,我们需要将背景颜色更改为黑色而不是透明。但是如何实现这种行为呢? ?
首先,我们需要知道应用栏是折叠还是展开;基于此,我们可以显示应用栏的正确内容。
为此,我们可以使用
NotificationListener小部件并监听ScrollNotifications来检测滚动偏移量的变化。基于此,我们可以知道应用栏是折叠还是展开。@override Widget build(BuildContext context) { ... /// 1 final scrollController = useScrollController(); final isCollapsed = useState(false); return NotificationListener<ScrollNotification>( onNotification: (notification) { /// 2 isCollapsed.value = scrollController.hasClients && scrollController.offset > (expandedBarHeight - collapsedBarHeight); return false; }, child: Stack( children: [ BlurredBackdropImage(movieDetails: movieDetails), CustomScrollView( controller: scrollController, slivers: [ SliverAppBar( expandedHeight: expandedBarHeight, collapsedHeight: collapsedBarHeight, centerTitle: false, pinned: true, /// 3 title: AnimatedOpacity( duration: const Duration(milliseconds: 200), opacity: isCollapsed.value ? 1 : 0, child: CollapsedAppBarContent(movieDetails: movieDetails), ), elevation: 0, /// 4 backgroundColor: isCollapsed.value ? Colors.black : Colors.transparent, leading: const BackButton( color: Colors.white, ), flexibleSpace: FlexibleSpaceBar( background: ExpandedAppBarContent( movieDetails: movieDetails, ), ), ), ... ], ), ], ), ); }
- 使用两个 hooks,
useScrollController()hook 来创建ScrollController,以及一个初始值为false的useState()hook 来创建isCollapsed状态对象。isCollapsed标志将用于根据其值更新应用栏内容。 - 通过检查
scrollController对象是否已附加到滚动视图来更新isCollapsed状态值。此外,它会检查当前滚动偏移量是否大于expandedBarHeight和collapsedBarHeight之间的差值。 - 将
CollapsedAppBarContent小部件包装在AnimatedOpacity小部件中,以根据isCollapsed值隐藏和显示内容。 - 根据
isCollapsed值更改backgroundColor。
现在应用栏将如下所示
太棒了,我们成功了! ? ?
奖励 ?
我们还可以为应用栏折叠时添加触觉反馈,以进一步改善用户体验。我们可以通过使用 HapticFeedback 类来实现这一点,该类允许访问设备上的触觉反馈接口。
@override Widget build(BuildContext context) { ... final didAddFeedback = useState(false); return NotificationListener<ScrollNotification>( onNotification: (notification) { ... /// When the app bar is collapsed and the feedback /// hasn't been added previously will invoke /// the `mediumImpact()` method, otherwise will /// reset the didAddFeedback value. /// if (isCollapsed.value && !didAddFeedback.value) { HapticFeedback.mediumImpact(); didAddFeedback.value = true; } else if (!isCollapsed.value) { didAddFeedback.value = false; } return false; }, child: Stack( children: [ ... ], ), ], ), ); }
我们添加了另一个名为 didAddFeedback 的 hook 状态变量,基于此它将调用
HapticFeedback.mediumImpact()方法。总结 ?
slivers 帮助您自定义滚动行为并构建复杂的滚动效果。掌握 slivers 将使您能够改善用户界面,更重要的是,改善用户体验。
感谢阅读! ❤️
GitHub






