Flutter 组件更新 | PinnedHeaderSliver @v3.24

1,276 阅读4分钟

PinnedHeaderSliver.png

在 Flutter 3.24 中增加了两位新和头部相关的 Sliver 组件成员,本文来看一下 PinnedHeaderSliver 为我们带来的便利。下面是本文基于 PinnedHeaderSliver 实现的三个案例。已经收录到了 FlutterUnit 开源项目 中,文中详细的源码可查阅对应组件:

官方案例标题吸顶效果标题+搜索吸顶
01.gif03.gif06.gif

1. PinnedHeaderSliver 的基本使用

如果官方有一个组件的案例,都会在该组件的源码注释中给出对应的位置。可以在 SDK 目录下按照路径找到它。我们就先从 PinnedHeaderSliver 的官方案例开始认识它的使用:

04.png


官方案例的效果如下所示,头部浅紫色的区域是 滑动视口 中的一部分,在滑动过程中会始终保持在顶部。点击右下角按钮时头部的文字会在轮训切换为一行和两行。

滑动两行
05.gif06.gif

我们知道 Sliver 系列的组件只能使用在滑动视口之下, 案例中使用了 CustomScrollView 组件构建了可滑动的区域, PinnedHeaderSliver 作为第一个滑片, 构造入参传入 header 组件:

---->[node_01.dart]----
CustomScrollView(
    controller: scrollController,
    slivers: <Widget>[
      PinnedHeaderSliver(child: header),
      const _ItemList(),
    ],
  ),
),

这里的 header 是一个普通的组件,会根据计数器是否是奇数数展示 1 行还是 2 行文字:

    final Widget header = Container(
      color: colorScheme.surface,
      padding: const EdgeInsets.all(4),
      child: Material(
        color: colorScheme.primaryContainer,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
          side: BorderSide(
            width: 7,
            color: colorScheme.outline,
          ),
        ),
        child: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.symmetric(vertical: 48),
          child: Text(
            count.isOdd ? 'Alternative Title\nWith Two Lines' : 'PinnedHeaderSliver',
            style: theme.textTheme.headlineMedium!.copyWith(
              color: colorScheme.onPrimaryContainer,
            ),
          ),
        ),
      ),
    );

总的来看,PinnedHeaderSliver 还是非常简单的,它的构造入参中只有 child 和 key。也就是说该组件的作用就是:

将一个 普通组件 以滑片的身份入住滑动视口,并且滑动到顶部时,会停留而不滑出视口。

07.png

它是一个简化版的 SliverPersistentHeader,不要自定义 SliverPersistentHeaderDelegate 就可以轻松完成吸顶效果。可能看这个案例体会不到这个组件的实用价值,下面继续看一下我设计的小案例。


2. PinnedHeaderSliver 实现标题吸顶

如下所示,顶部的标题在下滑消失过程中,会以透明度变换逐渐展示在顶部的中间。上滑出现时,顶部的标题也会逐渐消失。这里的顶部标题就是使用到了 PinnedHeaderSliver :

下滑上滑
08.gif09.gif

由于滑动过程中顶部栏始终不动,所以它是 PinnedHeaderSliver,下面的 Settings 文字是 SliverToBoxAdapter 滑片,所以滑动过程中 Settings 文字会移出视口。

10.png

既然上面官方代码中可以在构建头部时动态设置一行还是两行,那我们也可以动态设置标题文字的透明度。现在最关键的是如何的到滑动过程中 滑出的分度值,来确定标题文字的透明度。
如下所示,基于 SliverLayoutBuilder 组件可以感知滑片的 滑动约束 。这和 LayoutBuilder 组件的作用是类似的,其中可以根据滑动的偏移量 scrollOffset 和头部栏的高度计算出分度值:

11.png

---->[node_02.dart]----
Widget _buildSliverBar() {
  const Icon icon = Icon(CupertinoIcons.settings, color: Colors.blue);
  const TextStyle style = TextStyle(fontSize: 16, fontWeight: FontWeight.bold);
  const Text text = Text('Settings', style: style);
  Widget action = IconButton(onPressed: () {}, icon: icon );
  return SliverLayoutBuilder(builder: (_, scs) {
    double factor = (scs.scrollOffset / kToolbarHeight).clamp(0, 1);
    factor = factor < 0.2 ? 0 : factor;
    AppBar header = AppBar(
      backgroundColor: Colors.white,
      surfaceTintColor: Colors.transparent,
      actions: [action],
      centerTitle: true,
      title: Opacity(opacity: factor, child: text),
    );
    return PinnedHeaderSliver(child: header);
  });
}

Widget _buildTitleText() {
  const TextStyle style = TextStyle(fontSize: 20, fontWeight: FontWeight.bold);
  const Text text = Text('Settings', style: style);
  return const SliverToBoxAdapter(
    child: Padding(
      padding: EdgeInsets.only(left: 12.0, bottom: 8),
      child: text,
    ),
  );
}

3. 多个 PinnedHeaderSliver 的基本使用

细心的朋友可能发现了,上面效果中分隔线在滑动到顶部时也吸住了,不会滑出视口。PinnedHeaderSliver 组件可以同时存在多个。在其上方的普通 Sliver 滑出视口后,下方的 PinnedHeaderSliver 会停留在顶部。后续的普通 Sliver 可继续上滑:

12.png

所以代码中只需要添加如下代码即可:

---->[node_02.dart]----
CustomScrollView(
  controller: scrollController,
  slivers: <Widget>[
    _buildSliverBar(),
    _buildTitleText(),
    const PinnedHeaderSliver(child: Divider()), ///++ add divier
    const _ItemList(),
  ],
),

那么最后一个效果的实现方案就呼之欲出了: 我们可以将输入框放下 PinnedHeaderSliver 中,即可完成输入框在下滑过程中吸顶。

下滑上滑
13.gif14.gif
---->[node_03.dart]----
Widget _buildSliverSearch() {
  BoxDecoration decoration = BoxDecoration(
      color: const Color(0xffefeff1), borderRadius: BorderRadius.circular(6));
  Widget prefix = const Padding(
    padding: EdgeInsets.only(left: 8.0),
    child: Icon(CupertinoIcons.search, size: 20, color: Color(0xff808082))
  );
  return PinnedHeaderSliver(
      child: ColoredBox(
        color: Colors.white,
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.only(bottom: 8.0, right: 12, left: 12),
              child: CupertinoTextField(
                readOnly: true,
                placeholder: '搜索',
                onTap: _toSearchPage,
                decoration: decoration,
                prefix:prefix,
                style: const TextStyle(fontSize: 14),
                placeholderStyle: const TextStyle(color: Color(0xff808082)),
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
              ),
            ),
            const Divider(),
          ],
        ),
      ));
}

尾声

总的来看,PinnedHeaderSliver 是一个非常简单,但也非常强大的组件。它可以在滑动视口中放置多个,让你随心所欲地可以看中哪些部位可以吸顶。那本文就到这里,如果 FlutterUnit 系列对你有所帮助,还请在 Github 上多多 Start 。我们下篇再见~


更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。