重识Flutter — 探索Slivers的奇妙世界(综合实例)

前言

在前三篇文章中,从为什么要使用Sliver,再根据使用频率逐个解析Slivers系列的组件。相信您已经入门了Sliver的世界。为了更好的将Slivers相关的组件结合起来使用,本文将通过一个综合的案例来帮助你理解。

源代码:https://www.aliyundrive.com/s/mPCDFwRv4Rm

效果图

话不多说,先上效果图,有图有真相!

页面框架搭建

顶部
SliverAppBar(
  //指定状态栏(status bar)的亮度为暗色
  systemOverlayStyle:
  const SystemUiOverlayStyle(statusBarBrightness: Brightness.dark),
  expandedHeight: 275.0,
  backgroundColor: Colors.white,
  elevation: 0.0,
  pinned: true,
  stretch: true,
  flexibleSpace: FlexibleSpaceBar(
    background: Image.asset(
      'assets/images/back_image.png',
      fit: BoxFit.cover,
    ),
    stretchModes: const [
      StretchMode.blurBackground,
      StretchMode.zoomBackground,
    ],
  ),
  leadingWidth: 80.0,
  //裁剪为圆角矩形
  leading: ClipRRect(
    borderRadius: BorderRadius.circular(56.0),
    //模糊滤镜
    child: BackdropFilter(
      filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
      child: Container(
        height: 56.0,
        width: 56.0,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.white.withOpacity(0.20),
        ),
        child: SvgPicture.asset('assets/images/icon/arrow-ios-back-outline.svg'),
      ),
    ),
  ),
);
底部装饰

bottom: PreferredSize(
  preferredSize: const Size.fromHeight(0.0),
  child: Container(
    height: 32.0,
    alignment: Alignment.center,
    decoration: const BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(32.0),
        topRight: Radius.circular(32.0),
      ),
    ),
    child: Container(
      width: 40.0,
      height: 5.0,
      decoration: BoxDecoration(
        color: kOutlineColor,
        borderRadius: BorderRadius.circular(100.0),
      ),
    ),
  ),
),

使用SliverToBoxAdapter来使用基于Box协议的组件

SliverToBoxAdapter(
  child: Padding(
    padding: EdgeInsets.symmetric(horizontal: 20),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '意大利面拌42号混凝土',
          style: Theme.of(context).textTheme.titleMedium,
        ),
      	...
      ],
    ),
  ),
),

通过SliverPersistentHeader制作菜品展示区域

class Menu extends SliverPersistentHeaderDelegate {
    ...

  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    ...
    return Container(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
        	...
          Text(
               '菜品展示',
               style: Theme.of(context).textTheme.titleMedium,
          ),
          Expanded(
            child: Stack(
              children: [
              	//控制层叠关系
                if (percent > uploadlimit) ...[
                  card,
                  bottomsliverbar
                ] else ...[
                  bottomsliverbar,
                  card
                ]
              ],
            ),
          ),
        ],
      ),
    );
  }
	
  double get maxExtent => maxExtended;

  
  double get minExtent => minExtended;

  
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
      false;
}
反转叠加动画通过Stack结合Transform实现

Widget build(
    BuildContext context, double shrinkOffset, bool overlapsContent) {
	//shrinkOffset为SliverPersistentHeader滚动偏移量,用于对应图片的偏移程度
  final percent = shrinkOffset / 180;
	//限制图片偏移的触发范围
  final uploadlimit = 13 / 120;
	//使用clamp限制范围
  final valueback = (1 - percent - 0.77).clamp(0, uploadlimit);
	//将percent的值取平方,用于菜品展示图片下方背景块的位置偏移
  final fixrotation = pow(percent, 1.5);
	//背景
  final bottomsliverbar = _CustomBottomSliverBar(
      size: size, fixrotation: fixrotation, percent: percent);
	//菜品图片
  final card = _CoverCard(
      valueback: valueback,
      size: size,
      percent: percent,
      uploadlimit: uploadlimit);
  return Container(
    ...
  );
}
图片变换的布局

使用 Matrix4.identity()..rotateZ(...)实现绕 Z 轴的旋转变换。

Positioned(
    top: size.height * 0.005,
    left: size.width / 24,
    child: Transform(
      alignment: Alignment.topRight,
      transform: Matrix4.identity()
        ..rotateZ(percent > uploadlimit
            ? (valueback * angleForCard)
            : percent * angleForCard),
      child: CoverPhoto(size: size),
 ))

//CoverPhoto
Container(
	...
  decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(12),
      image: DecorationImage(
        ...
        fit: BoxFit.cover,
      )),
)
图片不规则背景+修复动画

背景块通过CustomPainter进行绘制


void paint(Canvas canvas, Size size) {
  final paint = Paint();
  paint.color = backgroundcolor;
  paint.style = PaintingStyle.fill;
  paint.strokeWidth = 10;
  final path = Path();
  path.moveTo(0, size.height);
  path.lineTo(size.width, size.height);
  path.lineTo(size.width, 0);
  path.lineTo(size.width * 0.27, 0);
  canvas.drawPath(path, paint);
}

修复动画通过Positionedleft做出视觉上的一个视差

Positioned(
    right: 0,
    bottom: 0,
    left: -size.width * fixrotation.clamp(0, 0.35),
    child: Container(
      height: size.height * 0.12,
      child: Stack(
        fit: StackFit.expand,
        children: [
          CustomPaint(
            painter: CutRectangle(),
          )
        ],
      ),
  ))

剩余部分

剩余部分都是通过SliverToBoxAdapter来进行实现,具体布局的内容不是本文的重点,就不过多阐述了,详见源代码。

总结

至此,三篇组件分解文章+一篇综合实战文章,我们学习了Sliver的使用和特性,相信您已经进入了Sliver的世界。我所写的也只是它魅力的冰山一角Sliver系列组件是用于创建灵活的滚动界面和复杂布局的关键,那么请继续探索Sliver的世界,利用其强大的特性和灵活的组合方式,创建出更加有趣和具有交互性的滚动界面吧~(后续还会有更多的使用教程、源码分解…)

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程的平行世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值