Flutter使用SliverAppBar开发更多样式AppBar
代码参考于【无聊的编码】Flutter Sliver全家桶初体验,一起炫酷一下,有优化
只是用官方的SliverAppBar只能做到如下样式:
但是我们想要的结果是下面这样的:
为此,我封装了一个组件SliverCustomAppBar用来实现。
在我封装的这个组件中有以下几个参数:
double height
:封面图片和信息显示区域的总高度String cover
:封面图片的地址(这里我使用的是加载资源图片的方法,实际应用中应该更改)double coverHeight
:封面图片所占有的高度double radius
:信息显示区域上方两个角的圆角值String avatar
:头像图片(这里我使用的是加载资源图片的方法,实际应用中应该更改)double avatarTop
:头像离顶部的距离double avatarLeft
:头像离左边的距离double avatarSize
:头像的尺寸Widget child
:信息区域显示的内容List<Widget> slivers
:SliverAppBar下方显示的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()],
),
);
}
}
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()],
),
);
}
}