头部折叠
使用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.dartSliverAppBar(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( //标题栏底部内容,此处为TabBarpreferredSize: 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的值从当前的值渐渐回到 0setState(() {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);@overrideMineTwoPageState 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;@overridevoid initState() {// TODO: implement initStateinit();super.initState();}@overridevoid dispose() {// TODO: implement disposesuper.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的值从当前的值渐渐回到 0setState(() {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;}});}@overrideWidget 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()],),),);}}
