我用条子做的有点像这个
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
@override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: DefaultTabController(
length: 4,
child: NestedScrollView(
body: TabBarView(children: [
//pages of tabBar
]),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
//for the pinned appBar
elevation: 0.0,
backgroundColor: Colors.red,
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(backgroundColor: Colors.red.withOpacity(0.4),
child: Icon(Icons.arrow_back,color: Colors.white,),
),
),
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
"https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&h=350",
fit: BoxFit.cover,
),
),
expandedHeight: 150,
),
SliverList(delegate: SliverChildListDelegate([
// for bio and other stuff
])),
SliverAppBar(
toolbarHeight: 0.0,
primary: false,
pinned: true,
bottom: TabBar(tabs: [
// tab bars
],),
)
];
},
),
),
),
);
}
}
我尝试在我的条子列表中添加堆栈,但 Circle Avatar 显示在应用栏后面。
我们如何才能实现这样的设计,或者实现它的替代方法是什么。
我通过使用 SliverPersistentHeader + 裁剪标题图像来完成设计。
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
const redOrange = Color(0xffb93303);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: CustomScrollView(
physics: const ClampingScrollPhysics(),
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: ExampleAppBar(),
),
SliverFillRemaining(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
SizedBox(height: 20),
Text(
'NASA',
style: TextStyle(
color: Colors.white70,
fontSize: 20,
fontWeight: FontWeight.w800,
),
),
],
),
),
),
],
),
);
}
}
class ExampleAppBar extends SliverPersistentHeaderDelegate {
final bottomHeight = 60;
final extraRadius = 5;
@override
Widget build(context, shrinkOffset, overlapsContent) {
final imageTop =
-shrinkOffset.clamp(0.0, maxExtent - minExtent - bottomHeight);
final double clowsingRate = (shrinkOffset == 0
? 0.0
: (shrinkOffset / (maxExtent - minExtent - bottomHeight)))
.clamp(0, 1);
final double opacity = shrinkOffset == minExtent
? 0
: 1 - (shrinkOffset.clamp(minExtent, minExtent + 30) - minExtent) / 30;
return Stack(
children: [
Positioned(
bottom: 0,
right: 20,
left: 45,
child: Row(
children: [
Transform.scale(
scale: 1.9 - clowsingRate,
alignment: Alignment.bottomCenter,
child: const _Avatar(),
),
const Spacer(),
const _Button(),
],
),
),
Positioned(
top: imageTop,
left: 0,
right: 0,
child: ClipPath(
clipper: InvertedCircleClipper(
radius: (1.9 - clowsingRate) * bottomHeight / 2 + extraRadius,
offset: Offset(
bottomHeight / 2 + 45,
(maxExtent - bottomHeight + extraRadius / 2) +
clowsingRate * bottomHeight / 2,
),
),
child: SizedBox(
height: maxExtent - bottomHeight,
child: ColoredBox(
color: redOrange,
child: Opacity(
opacity: opacity,
child: Image.network(
'https://i.ibb.co/RNCkztc/PIA24343-03-Mars-Perseverance-Landing-Graphics-Orange-Rover.webp',
fit: BoxFit.cover,
),
),
),
),
),
),
Positioned(
top: MediaQuery.of(context).padding.top + 5,
left: 10,
right: 10,
child: Row(
children: const [
_IconButton(
icon: Icons.arrow_back,
),
Spacer(),
_IconButton(
icon: Icons.more_vert,
),
],
)),
],
);
}
@override
double get maxExtent => 250;
@override
double get minExtent => 100;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
true;
}
class _IconButton extends StatelessWidget {
const _IconButton({
required this.icon,
});
final IconData icon;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: redOrange.withOpacity(0.5),
),
padding: const EdgeInsets.all(4),
child: Icon(
icon,
color: Colors.white,
),
);
}
}
class _Button extends StatelessWidget {
const _Button();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(color: Colors.blueAccent, width: 2),
),
child: const Text(
'Follow',
style: TextStyle(
color: Colors.blueAccent,
fontWeight: FontWeight.w700,
),
),
);
}
}
class InvertedCircleClipper extends CustomClipper<Path> {
const InvertedCircleClipper({
required this.offset,
required this.radius,
});
final Offset offset;
final double radius;
@override
Path getClip(size) {
return Path()
..addOval(Rect.fromCircle(
center: offset,
radius: radius,
))
..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..fillType = PathFillType.evenOdd;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
class _Avatar extends StatelessWidget {
const _Avatar({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 60,
width: 60,
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
width: 1.5,
),
borderRadius: BorderRadius.circular(50),
),
padding: const EdgeInsets.all(2),
child: Image.network(
'https://i.ibb.co/YdTh4Xx/logo.png',
),
);
}
}