Flutter使用SliverAppBar开发更多样式AppBar

代码参考于【无聊的编码】Flutter Sliver全家桶初体验,一起炫酷一下,有优化

只是用官方的SliverAppBar只能做到如下样式:

image

但是我们想要的结果是下面这样的:

image

为此,我封装了一个组件SliverCustomAppBar用来实现。

在我封装的这个组件中有以下几个参数:

  • double height:封面图片和信息显示区域的总高度
  • String cover:封面图片的地址(这里我使用的是加载资源图片的方法,实际应用中应该更改)
  • double coverHeight:封面图片所占有的高度
  • double radius:信息显示区域上方两个角的圆角值
  • String avatar:头像图片(这里我使用的是加载资源图片的方法,实际应用中应该更改)
  • double avatarTop:头像离顶部的距离
  • double avatarLeft:头像离左边的距离
  • double avatarSize:头像的尺寸
  • Widget child:信息区域显示的内容
  • List<Widget> sliversSliverAppBar下方显示的Sliver组件

下面是使用该组件的代码和显示效果:

class CustomAppBarPage extends StatelessWidget {
  const CustomAppBarPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SliverCustomAppBar(
        height: 372.h,
        cover: 'assets/images/cover.jpg',
        coverHeight: 210.h,
        radius: 12.h,
        avatar: 'assets/images/avatar.jpg',
        avatarTop: 174.h,
        avatarLeft: 15.w,
        avatarSize: 80.h,
        child: Column(children: []),
        slivers: [BuildList()],
      ),
    );
  }
}

image

SliverCustomAppBar的详细代码,在实际使用前请详细查看TODO注释处的内容:

class SliverCustomAppBar extends StatefulWidget {
  const SliverCustomAppBar({
    Key? key,
    required this.height,
    required this.cover,
    required this.coverHeight,
    required this.radius,
    required this.avatar,
    required this.avatarTop,
    required this.avatarLeft,
    required this.avatarSize,
    required this.child,
    required this.slivers,
  }) : super(key: key);

  final double height;
  final String cover;
  final double coverHeight;
  final double radius;
  final String avatar;
  final double avatarTop;
  final double avatarLeft;
  final double avatarSize;
  final Widget child;
  final List<Widget> slivers;

  @override
  State<SliverCustomAppBar> createState() => _SliverCustomAppBarState();
}

class _SliverCustomAppBarState extends State<SliverCustomAppBar>
    with SingleTickerProviderStateMixin {
  late AnimationController animationController;
  late Animation<double> animation;

  double startDy = 0;
  double expendHeight = 0;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    animation = Tween(begin: 0.0, end: 0.0).animate(animationController);
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Listener(
        onPointerMove: (e) {
          // 记录初始位置的信息
          if (startDy == 0) startDy = e.position.dy;
          // 滑动的距离 = 滑动到的位置 - 初始位置(防止滑动太长所以除以3)
          expendHeight = (e.position.dy - startDy) / 3;
          setState(() {});
        },
        onPointerUp: (e) {
          startDy = 0;
          animation =
              Tween(begin: expendHeight, end: 0.0).animate(animationController)
                ..addListener(() {
                  expendHeight = animation.value;
                  setState(() {});
                });
          animationController.forward(from: 0.0);
        },
        child: ScrollConfiguration(
          // 防止下拉时顶部出现水波纹效果
          behavior: NoShadowScrollBehavior(),
          // TODO:实际应用中大部分应该改为NestedScrollView
          child: CustomScrollView(
            slivers: [
              BuildSliverAppBar(
                expendHeight: expendHeight,
                height: widget.height,
                cover: widget.cover,
                coverHeight: widget.coverHeight,
                radius: widget.radius,
                avatarTop: widget.avatarTop,
                avatarLeft: widget.avatarLeft,
                avatarSize: widget.avatarSize,
                avatar: widget.avatar,
                child: widget.child,
              ),
              ...widget.slivers,
            ],
          ),
        ),
      ),
    );
  }
}
class BuildSliverAppBar extends StatelessWidget {
  BuildSliverAppBar({
    super.key,
    required this.expendHeight,
    required this.height,
    required this.cover,
    required this.coverHeight,
    required this.radius,
    required this.avatarTop,
    required this.avatarLeft,
    required this.avatarSize,
    required this.child,
    required this.avatar,
  });

  final double height;
  final double expendHeight;
  final String cover;
  final double coverHeight;
  final double radius;
  final String avatar;
  final double avatarTop;
  final double avatarLeft;
  final double avatarSize;
  final Widget child;

  final double _statusBarHeight = MediaQueryData.fromWindow(window).padding.top;
  final _screenWidth = MediaQueryData.fromWindow(window).size.width;

  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      pinned: true,
      expandedHeight: height - _statusBarHeight + expendHeight,
      flexibleSpace: FlexibleSpaceBar(
        collapseMode: CollapseMode.pin,
        background: Stack(
          children: [
            SizedBox(
              height: coverHeight + expendHeight,
              width: _screenWidth,
              // TODO:更改以下代码
              child: Image.asset(cover, fit: BoxFit.cover),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Container(
                width: _screenWidth,
                height: height - coverHeight + radius,
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.vertical(
                    top: Radius.circular(radius),
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(.1),
                      offset: const Offset(0, -2),
                      blurRadius: 2.h,
                    ),
                  ],
                ),
                child: child,
              ),
            ),
            Positioned(
              top: avatarTop + expendHeight,
              left: avatarLeft,
              child: Container(
                height: avatarSize,
                width: avatarSize,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(avatarSize)),
                  border: Border.all(color: Colors.white, width: 2.h),
                  image: DecorationImage(
                    // TODO:更改以下代码
                    image: AssetImage(avatar),
                    fit: BoxFit.cover,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

NoShadowScrollBehavior详细代码:

import 'package:flutter/material.dart';

/// 隐藏水波纹配置
class NoShadowScrollBehavior extends ScrollBehavior {
  @override
  Widget buildOverscrollIndicator(
      BuildContext context, Widget child, ScrollableDetails details) {
    // TODO: implement buildOverscrollIndicator
    // return super.buildOverscrollIndicator(context, child, details);
    return child;
  }
}

使用示例:

class CustomAppBarPage extends StatelessWidget {
  const CustomAppBarPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SliverCustomAppBar(
        height: 372.h,
        cover: 'assets/images/cover.jpg',
        coverHeight: 210.h,
        radius: 12.h,
        avatar: 'assets/images/avatar.jpg',
        avatarTop: 174.h,
        avatarLeft: 15.w,
        avatarSize: 80.h,
        child: Column(children: []),
        slivers: [BuildList()],
      ),
    );
  }
}
posted @ 2023-02-10 10:14  菠萝橙子丶  阅读(506)  评论(3编辑  收藏  举报