头部折叠

使用NestedScrollView实现嵌套列表

头部使用SliverAppBar实现折叠,内容实用TabBarView + ListView实现内容列表

  1. _scrollController.addListener(() {
  2. // print(_scrollController.offset);
  3. if (_scrollController.offset > 159.0 && !showTitle) {
  4. setState(() {
  5. showTitle = true;
  6. });
  7. }
  8. if (_scrollController.offset < 159 && showTitle) {
  9. setState(() {
  10. showTitle = false;
  11. });
  12. }
  13. });
  14. NestedScrollView(
  15. controller: _scrollController,
  16. headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  17. return <Widget>[
  18. SliverOverlapAbsorber(
  19. handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
  20. sliver: MineHeaderWidget(
  21. showTitle: showTitle,
  22. bgColor: AppBarBgColor,
  23. textColor: AppBarTextColor,
  24. tabController: _tabController,
  25. extraPicHeight: extraPicHeight,
  26. ),
  27. ),
  28. ];
  29. },
  30. body: TabBarView(
  31. controller: _tabController,
  32. children: [MineNotesWidget(), MineNotesWidget(), MineNotesWidget()],
  33. ),
  34. ),

sliverAppBar

使用NestedScrollView的controller监听滚动位置,当头像滚上去,在title显示头像

  1. MineHeader.dart
  2. SliverAppBar(
  3. floating: false, //标题栏是否悬浮
  4. snap: false, //配合floating使用
  5. pinned: true, //标题栏是否固定
  6. expandedHeight: 290.0 + extraPicHeight, //伸缩高度
  7. forceElevated: true, //是否显示阴影
  8. leading: IconButton( //标题栏左侧按钮,多为返回按钮
  9. icon: Icon(Icons.menu, color: textColor),
  10. onPressed: () {
  11. Application.navigateTo(context, '/setting');
  12. },
  13. ),
  14. title: showTitle //使用showTitle展示头像
  15. ? Center(
  16. child: CircleAvatar(
  17. radius: 18,
  18. backgroundImage: NetworkImage(
  19. 'https://pic2.zhimg.com/v2-639b49f2f6578eabddc458b84eb3c6a1.jpg'),
  20. ),
  21. )
  22. : Text(''),
  23. actions: [ // appbar右侧按钮
  24. InkWell(
  25. child: Container(
  26. color: Colors.transparent,
  27. padding: EdgeInsets.symmetric(horizontal: 10),
  28. child: Icon(Icons.share, color: textColor),
  29. ),
  30. onTap: () {},
  31. ),
  32. ],
  33. backgroundColor: bgColor, //标题栏背景颜色
  34. bottom: PreferredSize( //标题栏底部内容,此处为TabBar
  35. preferredSize: Size.fromHeight(40),
  36. child: Container(
  37. decoration: BoxDecoration(
  38. color: Colors.white70,
  39. borderRadius: BorderRadius.only(
  40. topLeft: Radius.circular(20), topRight: Radius.circular(20)),
  41. ),
  42. padding: EdgeInsets.symmetric(horizontal: 70),
  43. child: TabBar(
  44. controller: tabController,
  45. labelColor: Colors.black87,
  46. labelPadding: EdgeInsets.symmetric(vertical: 12),
  47. indicatorPadding: EdgeInsets.symmetric(horizontal: 20),
  48. tabs: [Text('笔记'), Text('收藏'), Text('赞过')],
  49. ),
  50. ),
  51. ),
  52. flexibleSpace: FlexibleSpaceBar( // 伸缩内容
  53. collapseMode: CollapseMode.pin,
  54. background: Container(
  55. height: 290 + extraPicHeight, // 背景图片高度
  56. width: double.infinity,
  57. decoration: BoxDecoration(
  58. image: DecorationImage(
  59. image: AssetImage("assets/images/login/login_bg_1.jpg"),
  60. fit: BoxFit.fill,
  61. ),
  62. ),
  63. child: Stack(
  64. fit: StackFit.expand,
  65. children: [
  66. Positioned(
  67. child: Container(
  68. width: double.infinity,
  69. height: double.infinity,
  70. color: Color(0x99000000),
  71. ),
  72. ),
  73. ],
  74. ),
  75. ),
  76. ),
  77. )

头部图拉伸效果

使用Listener监听下滑动作

计算下滑距离值,使头图高度加距离实现拉伸效果

  1. late AnimationController _animationController;
  2. Animation<double>? anim;
  3. double extraPicHeight = 0;
  4. double prev_dy = 0;
  5. runAnimate() {
  6. //设置动画让extraPicHeight的值从当前的值渐渐回到 0
  7. setState(() {
  8. anim =
  9. Tween(begin: extraPicHeight, end: 0.0).animate(_animationController)
  10. ..addListener(() {
  11. setState(() {
  12. extraPicHeight = anim!.value;
  13. });
  14. });
  15. prev_dy = 0; //同样归零
  16. });
  17. }
  18. updatePicHeight(changed) {
  19. if (prev_dy == 0) {
  20. //如果是手指第一次点下时,我们不希望图片大小就直接发生变化,所以进行一个判定。
  21. prev_dy = changed;
  22. }
  23. extraPicHeight += changed - prev_dy; //新的一个y值减去前一次的y值然后累加,作为加载到图片上的高度。
  24. setState(() {
  25. //更新数据
  26. prev_dy = changed;
  27. if (extraPicHeight < 0) { //不能使extraPicHeight小于0,
  28. extraPicHeight = 0;
  29. } else {
  30. extraPicHeight = extraPicHeight;
  31. }
  32. });
  33. }
  34. Listener(
  35. onPointerMove: (result) {
  36. if (_scrollController.offset == 0) {
  37. updatePicHeight(result.position.dy);
  38. }
  39. },
  40. onPointerUp: (_) { //抬手归0动画
  41. runAnimate(); //动画执行
  42. _animationController.forward(from: 0);
  43. },
  44. childe: NestedScrollView()
  45. }
  1. import 'package:flutter/material.dart';
  2. import 'package:myapp/components/mine/mine_header.dart';
  3. import 'package:myapp/components/mine/mine_notes.dart';
  4. import 'package:palette_generator/palette_generator.dart';
  5. class MineTwoPage extends StatefulWidget {
  6. const MineTwoPage({Key? key}) : super(key: key);
  7. @override
  8. MineTwoPageState createState() => MineTwoPageState();
  9. }
  10. class MineTwoPageState extends State<MineTwoPage>
  11. with TickerProviderStateMixin {
  12. bool showTitle = false;
  13. List<String> _tabs = ["Tab 1", "Tab 2", "Tab 3"];
  14. Color AppBarBgColor = Color(0x55000000);
  15. Color AppBarTextColor = Colors.white;
  16. late TabController _tabController;
  17. late PaletteGenerator _paletteGenerator;
  18. late ScrollController _scrollController;
  19. late AnimationController _animationController;
  20. Animation<double>? anim;
  21. double extraPicHeight = 0;
  22. double prev_dy = 0;
  23. @override
  24. void initState() {
  25. // TODO: implement initState
  26. init();
  27. super.initState();
  28. }
  29. @override
  30. void dispose() {
  31. // TODO: implement dispose
  32. super.dispose();
  33. }
  34. void init() async {
  35. _scrollController = new ScrollController();
  36. _tabController = new TabController(length: 3, vsync: this);
  37. _animationController =
  38. AnimationController(vsync: this, duration: Duration(milliseconds: 300));
  39. anim = Tween(begin: 0.0, end: 0.0).animate(_animationController);
  40. _scrollController.addListener(() {
  41. // print(_scrollController.offset);
  42. if (_scrollController.offset > 159.0 && !showTitle) {
  43. setState(() {
  44. showTitle = true;
  45. });
  46. }
  47. if (_scrollController.offset < 159 && showTitle) {
  48. setState(() {
  49. showTitle = false;
  50. });
  51. }
  52. });
  53. _paletteGenerator = await PaletteGenerator.fromImageProvider(
  54. AssetImage('assets/images/login/login_bg_1.jpg'),
  55. size: Size(double.infinity, double.infinity),
  56. maximumColorCount: 20,
  57. );
  58. setState(() {
  59. AppBarBgColor = _paletteGenerator.dominantColor!.color;
  60. // AppBarTextColor = _paletteGenerator!.dominantColor!.bodyTextColor;
  61. });
  62. }
  63. runAnimate() {
  64. //设置动画让extraPicHeight的值从当前的值渐渐回到 0
  65. setState(() {
  66. anim =
  67. Tween(begin: extraPicHeight, end: 0.0).animate(_animationController)
  68. ..addListener(() {
  69. setState(() {
  70. extraPicHeight = anim!.value;
  71. });
  72. });
  73. prev_dy = 0; //同样归零
  74. });
  75. }
  76. updatePicHeight(changed) {
  77. if (prev_dy == 0) {
  78. //如果是手指第一次点下时,我们不希望图片大小就直接发生变化,所以进行一个判定。
  79. prev_dy = changed;
  80. }
  81. extraPicHeight += changed - prev_dy; //新的一个y值减去前一次的y值然后累加,作为加载到图片上的高度。
  82. setState(() {
  83. //更新数据
  84. prev_dy = changed;
  85. if (extraPicHeight < 0) {
  86. extraPicHeight = 0;
  87. } else {
  88. extraPicHeight = extraPicHeight;
  89. }
  90. });
  91. }
  92. @override
  93. Widget build(BuildContext context) {
  94. return Listener(
  95. onPointerMove: (result) {
  96. if (_scrollController.offset == 0) {
  97. updatePicHeight(result.position.dy);
  98. }
  99. },
  100. onPointerUp: (_) {
  101. runAnimate(); //动画执行
  102. _animationController.forward(from: 0);
  103. },
  104. child: NestedScrollView(
  105. controller: _scrollController,
  106. headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  107. return <Widget>[
  108. SliverOverlapAbsorber(
  109. handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
  110. sliver: MineHeaderWidget(
  111. showTitle: showTitle,
  112. bgColor: AppBarBgColor,
  113. textColor: AppBarTextColor,
  114. tabController: _tabController,
  115. extraPicHeight: extraPicHeight,
  116. ),
  117. ),
  118. ];
  119. },
  120. body: TabBarView(
  121. // These are the contents of the tab views, below the tabs.
  122. controller: _tabController,
  123. children: [MineNotesWidget(), MineNotesWidget(), MineNotesWidget()],
  124. ),
  125. ),
  126. );
  127. }
  128. }