头部折叠
使用NestedScrollView实现嵌套列表
头部使用SliverAppBar实现折叠,内容实用TabBarView + ListView实现内容列表
_scrollController.addListener(() {
// print(_scrollController.offset);
if (_scrollController.offset > 159.0 && !showTitle) {
setState(() {
showTitle = true;
});
}
if (_scrollController.offset < 159 && showTitle) {
setState(() {
showTitle = false;
});
}
});
NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: MineHeaderWidget(
showTitle: showTitle,
bgColor: AppBarBgColor,
textColor: AppBarTextColor,
tabController: _tabController,
extraPicHeight: extraPicHeight,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [MineNotesWidget(), MineNotesWidget(), MineNotesWidget()],
),
),
sliverAppBar
使用NestedScrollView的controller监听滚动位置,当头像滚上去,在title显示头像
MineHeader.dart
SliverAppBar(
floating: false, //标题栏是否悬浮
snap: false, //配合floating使用
pinned: true, //标题栏是否固定
expandedHeight: 290.0 + extraPicHeight, //伸缩高度
forceElevated: true, //是否显示阴影
leading: IconButton( //标题栏左侧按钮,多为返回按钮
icon: Icon(Icons.menu, color: textColor),
onPressed: () {
Application.navigateTo(context, '/setting');
},
),
title: showTitle //使用showTitle展示头像
? Center(
child: CircleAvatar(
radius: 18,
backgroundImage: NetworkImage(
'https://pic2.zhimg.com/v2-639b49f2f6578eabddc458b84eb3c6a1.jpg'),
),
)
: Text(''),
actions: [ // appbar右侧按钮
InkWell(
child: Container(
color: Colors.transparent,
padding: EdgeInsets.symmetric(horizontal: 10),
child: Icon(Icons.share, color: textColor),
),
onTap: () {},
),
],
backgroundColor: bgColor, //标题栏背景颜色
bottom: PreferredSize( //标题栏底部内容,此处为TabBar
preferredSize: Size.fromHeight(40),
child: Container(
decoration: BoxDecoration(
color: Colors.white70,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
padding: EdgeInsets.symmetric(horizontal: 70),
child: TabBar(
controller: tabController,
labelColor: Colors.black87,
labelPadding: EdgeInsets.symmetric(vertical: 12),
indicatorPadding: EdgeInsets.symmetric(horizontal: 20),
tabs: [Text('笔记'), Text('收藏'), Text('赞过')],
),
),
),
flexibleSpace: FlexibleSpaceBar( // 伸缩内容
collapseMode: CollapseMode.pin,
background: Container(
height: 290 + extraPicHeight, // 背景图片高度
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/login/login_bg_1.jpg"),
fit: BoxFit.fill,
),
),
child: Stack(
fit: StackFit.expand,
children: [
Positioned(
child: Container(
width: double.infinity,
height: double.infinity,
color: Color(0x99000000),
),
),
],
),
),
),
)
头部图拉伸效果
使用Listener监听下滑动作
计算下滑距离值,使头图高度加距离实现拉伸效果
late AnimationController _animationController;
Animation<double>? anim;
double extraPicHeight = 0;
double prev_dy = 0;
runAnimate() {
//设置动画让extraPicHeight的值从当前的值渐渐回到 0
setState(() {
anim =
Tween(begin: extraPicHeight, end: 0.0).animate(_animationController)
..addListener(() {
setState(() {
extraPicHeight = anim!.value;
});
});
prev_dy = 0; //同样归零
});
}
updatePicHeight(changed) {
if (prev_dy == 0) {
//如果是手指第一次点下时,我们不希望图片大小就直接发生变化,所以进行一个判定。
prev_dy = changed;
}
extraPicHeight += changed - prev_dy; //新的一个y值减去前一次的y值然后累加,作为加载到图片上的高度。
setState(() {
//更新数据
prev_dy = changed;
if (extraPicHeight < 0) { //不能使extraPicHeight小于0,
extraPicHeight = 0;
} else {
extraPicHeight = extraPicHeight;
}
});
}
Listener(
onPointerMove: (result) {
if (_scrollController.offset == 0) {
updatePicHeight(result.position.dy);
}
},
onPointerUp: (_) { //抬手归0动画
runAnimate(); //动画执行
_animationController.forward(from: 0);
},
childe: NestedScrollView()
}
import 'package:flutter/material.dart';
import 'package:myapp/components/mine/mine_header.dart';
import 'package:myapp/components/mine/mine_notes.dart';
import 'package:palette_generator/palette_generator.dart';
class MineTwoPage extends StatefulWidget {
const MineTwoPage({Key? key}) : super(key: key);
@override
MineTwoPageState createState() => MineTwoPageState();
}
class MineTwoPageState extends State<MineTwoPage>
with TickerProviderStateMixin {
bool showTitle = false;
List<String> _tabs = ["Tab 1", "Tab 2", "Tab 3"];
Color AppBarBgColor = Color(0x55000000);
Color AppBarTextColor = Colors.white;
late TabController _tabController;
late PaletteGenerator _paletteGenerator;
late ScrollController _scrollController;
late AnimationController _animationController;
Animation<double>? anim;
double extraPicHeight = 0;
double prev_dy = 0;
@override
void initState() {
// TODO: implement initState
init();
super.initState();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
void init() async {
_scrollController = new ScrollController();
_tabController = new TabController(length: 3, vsync: this);
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
anim = Tween(begin: 0.0, end: 0.0).animate(_animationController);
_scrollController.addListener(() {
// print(_scrollController.offset);
if (_scrollController.offset > 159.0 && !showTitle) {
setState(() {
showTitle = true;
});
}
if (_scrollController.offset < 159 && showTitle) {
setState(() {
showTitle = false;
});
}
});
_paletteGenerator = await PaletteGenerator.fromImageProvider(
AssetImage('assets/images/login/login_bg_1.jpg'),
size: Size(double.infinity, double.infinity),
maximumColorCount: 20,
);
setState(() {
AppBarBgColor = _paletteGenerator.dominantColor!.color;
// AppBarTextColor = _paletteGenerator!.dominantColor!.bodyTextColor;
});
}
runAnimate() {
//设置动画让extraPicHeight的值从当前的值渐渐回到 0
setState(() {
anim =
Tween(begin: extraPicHeight, end: 0.0).animate(_animationController)
..addListener(() {
setState(() {
extraPicHeight = anim!.value;
});
});
prev_dy = 0; //同样归零
});
}
updatePicHeight(changed) {
if (prev_dy == 0) {
//如果是手指第一次点下时,我们不希望图片大小就直接发生变化,所以进行一个判定。
prev_dy = changed;
}
extraPicHeight += changed - prev_dy; //新的一个y值减去前一次的y值然后累加,作为加载到图片上的高度。
setState(() {
//更新数据
prev_dy = changed;
if (extraPicHeight < 0) {
extraPicHeight = 0;
} else {
extraPicHeight = extraPicHeight;
}
});
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerMove: (result) {
if (_scrollController.offset == 0) {
updatePicHeight(result.position.dy);
}
},
onPointerUp: (_) {
runAnimate(); //动画执行
_animationController.forward(from: 0);
},
child: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: MineHeaderWidget(
showTitle: showTitle,
bgColor: AppBarBgColor,
textColor: AppBarTextColor,
tabController: _tabController,
extraPicHeight: extraPicHeight,
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
controller: _tabController,
children: [MineNotesWidget(), MineNotesWidget(), MineNotesWidget()],
),
),
);
}
}