【FlutterUnit周边】SliverPersistentHeader使用指南

9,373 阅读2分钟

零、前言

如果你进入过FlutterUnit,那么主页中头部的Tap栏你应该印象深刻。 如下效果: 在上滑时Tap栏会逐渐变矮,直到最小值。下拉到顶时变矮的Tap栏会逐渐变高,直到最大值 FlutterUnit本身主页比较复杂,本文就来写一个最简实践,用最少的代码来实现这个效果。 本文的主人公是SliverPersistentHeader,来一起看一下它的用法。

上滑效果下拉效果
滑动效果下拉效果

一、项目初始

1. 程序入口

在 main 函数中使用SystemChrome.setSystemUIOverlayStyle让状态栏变透明 测试 demo 的核心组件在 SliverPersistentHeaderDemo

void main() {
  //设置透明 状态栏
  SystemUiOverlayStyle style = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
  SystemChrome.setSystemUIOverlayStyle(style);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        debugShowCheckedModeBanner: false,
        theme: ThemeData( primarySwatch: Colors.blue  ),
        home: Scaffold(
          body: SliverPersistentHeaderDemo(),
        ));
  }
}

2.构建色彩列表

我们要构建的是比较复杂的滑动效果, 可以使用CustomScrollView,其中slivers接收Sliver家族组件的列表。CustomScrollView不是本文的主人公,这里不多说,以后会有专篇。 如下: _buildSliverList负责构建SliverList,其中颜色的item组件的构建交由_buildColorItem 。我建议大家可以把构建的粒度细分一下,不要什么都塞一块,这样看起来会比较清晰。

色彩列表色彩列表
image-20201025093338154image-20201025093440791
class SliverPersistentHeaderDemo extends StatelessWidget {
	// 色彩数据
  final List<Color> data = List.generate(24, (i) => Color(0xFFFF00FF - 24*i));

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
        slivers: <Widget>[
          // TODO 添加 bar
          _buildSliverList()
        ],
    );
  }

  // 构建颜色列表
  Widget _buildSliverList() =>
      SliverList(
        delegate: SliverChildBuilderDelegate(
                (_, int index) => _buildColorItem(data[index]),
            childCount: data.length),
      );

	// 构建颜色列表item
  Widget _buildColorItem(Color color) =>
      Card(
        child: Container(
          alignment: Alignment.center,
          width: 100,
          height: 60,
          color: color,
          child: Text(
            colorString(color),
            style: const TextStyle(
                color: Colors.white,
                shadows: [
                  Shadow(color: Colors.black,
                      offset: Offset(.5, .5),
                      blurRadius: 2)
                ]),
          ),
        ),
      );
  
	// 颜色转换为文字
  String colorString(Color color) =>
      "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}";
}

这样初始的demo就搭建好了,下面来看看SliverPersistentHeader的用法吧


二、认识 SliverPersistentHeader

1.SliverPersistentHeader属性一览
属性名类型默认值介绍
delegateSliverPersistentHeaderDelegaterequired组件构建代理
pinnedboolfalse是否固定
floatingboolfalse是否浮动

2.SliverPersistentHeaderDelegate的使用

估计很多人看到XXXDelegate就有种劝退的感觉。先别怕,看看它是什么。 可以看到它是抽象类,说明需要实现一些抽象方法,而一般抽象方法都会为我们回调一些有价值的东西 查看他的族谱,发现没有可以使用的子类,那么想使用它,二话不说,先写个他的子类。

image-20201025094150594

现在写一个UnitPersistentHeaderDelegate实现一下SliverPersistentHeaderDelegate, 可以看到有下面四个需要实现的方法: build,maxExtent,minExtent,shouldRebuild

class UnitPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    print(
        "=====shrinkOffset:$shrinkOffset======overlapsContent:$overlapsContent====");
    final String info =
        'shrinkOffset:${shrinkOffset.toStringAsFixed(1)}'
        '\noverlapsContent:$overlapsContent';
    return Container(
      alignment: Alignment.center,
      color: Colors.orangeAccent,
      child: Text(
        info,
        style: TextStyle(fontSize: 20, color: Colors.white),
      ),
    );
  }
  @override
  double get maxExtent => 120;
  
  @override
  double get minExtent => 80;
  
  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
      false;
}

先简单实现一下使用上面定义的UnitPersistentHeaderDelegate查看效果: 可以看到上面的build方法的作用就是构建组件,shrinkOffset为偏移量 头部栏组件开始完全展开maxExtent高度,随着列表上滑而上滑,可以从日志里看出最大上滑高度为maxExtent,这是默认pinned=false,floating=false的滑动效果。

上滑测试下拉测试
Delegate上滑测试Delegate下拉测试
    //英雄所见...
    return CustomScrollView(
      slivers: <Widget>[
        _buildPersistentHeader(), //<-- 在列表上方创建PersistentHeader
        _buildSliverList()
      ],
    );
  }
	
  // 使用UnitPersistentHeaderDelegate创建PersistentHeader
  Widget _buildPersistentHeader() => SliverPersistentHeader(
      delegate: UnitPersistentHeaderDelegate());


3.SliverPersistentHeader的pinned与floating属性

下面开始试验:

pinned_true_floating_falsepinned_true_floating_true
pinned_true_floating_falsepinned_true_floating_true
上滑:顶部会留出minExtent的高度,不再随上滑而减小上滑:顶部会留出minExtent的高度,不再随上滑而减小
下拉:直到滑到顶端时,剩余空间才会展开下拉: 任意位置下拉时, 剩余空间会展开

下面开始试验:

pinned_false_floating_falsepinned_false_floating_true
pinned_false_floating_falsepinned_false_floating_true_
上滑: 顶部会滑出上滑:顶部会滑出
下拉:直到滑到顶端时,顶部才会展开下拉: 任意位置下拉时, 空间会展开

三、使用 SliverPersistentHeader

1. 封装PersistentHeaderBuilder

上面使用起来比较麻烦,可以自定义一个PersistentHeaderBuilder来简化构建

使用builder属性,将创建的逻辑移交到使用时,可以回调一些有价值的数据,比如偏移量

class PersistentHeaderBuilder extends SliverPersistentHeaderDelegate {
  final double max;
  final double min;
  final Widget Function(BuildContext context, double offset) builder;

  PersistentHeaderBuilder(
      {this.max = 120, this.min = 80, @required this.builder})
      : assert(max >= min && builder != null);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return builder(context, shrinkOffset);
  }

  @override
  double get maxExtent => max;

  @override
  double get minExtent => min;

  @override
  bool shouldRebuild(covariant PersistentHeaderBuilder oldDelegate) =>
      max != oldDelegate.max ||
      min != oldDelegate.min ||
      builder != oldDelegate.builder;
}

2.使用PersistentHeaderBuilder
  Widget _buildPersistentHeader() => SliverPersistentHeader(
      pinned: true,
      floating: false,
      delegate: PersistentHeaderBuilder(builder: (ctx, offset) => Container(
        alignment: Alignment.center,
        color: Colors.orangeAccent,
        child: Text(
          "shrinkOffset:${offset.toStringAsFixed(1)}",
          style: TextStyle(fontSize: 20, color: Colors.white),
        ),
      )));

3.多个SliverPersistentHeader的使用

你也可以根据offset来进行一些变换处理。 多个SliverPersistentHeader是可以共存的,如下

上滑下拉
two_bartow_bar_2
Widget _buildPersistentHeader2() => SliverPersistentHeader(
    pinned: false,
    floating: false,
    delegate: PersistentHeaderBuilder(
        max: 100,
        builder: (ctx, offset) => Container(
              transform: Matrix4.rotationZ(offset / 120 * pi / 2),
              alignment: Alignment.center,
              color: Colors.blue,
              child: Text(
                "shrinkOffset:${offset.toStringAsFixed(1)}",
                style: TextStyle(fontSize: 20, color: Colors.white),
              ),
            )));

SliverPersistentHeader基本用法就是这样,你可以基于此实现很多有意思的滑动效果 最后欢迎关注我的开源项目 FlutterUnit,FlutterUnit相关的周边文章会陆续更新,其中包括一些Flutter组件的用法,或一些FlutterUnit实现的细节,FlutterUnit的重大更新等,欢迎持续关注。

@张风捷特烈 2020.10.25 未允禁转
~ END ~