如何在Flutter中实现Formulia应用式可折叠AppBar(类似SliverAppBar)?
实现类似Formulia的可折叠AppBar(Flutter)
Formulia的可折叠AppBar核心是滚动时动态调整栏内元素的大小、透明度和位置,单纯依赖SliverAppBar的默认属性无法实现这种精细化的过渡效果,需要结合滚动监听+自定义布局来完成。以下是完整的实现方案:
核心实现思路
- 使用
CustomScrollView作为滚动容器,搭配SliverAppBar作为基础栏 - 通过
ScrollController监听滚动偏移量,计算折叠进度(0=完全展开,1=完全收缩) - 在
SliverAppBar的flexibleSpace中自定义布局,根据折叠进度动态更新子组件的样式
完整代码示例
import 'package:flutter/material.dart'; class FormuliaStyleAppBar extends StatefulWidget { const FormuliaStyleAppBar({super.key}); @override State<FormuliaStyleAppBar> createState() => _FormuliaStyleAppBarState(); } class _FormuliaStyleAppBarState extends State<FormuliaStyleAppBar> { final ScrollController _scrollController = ScrollController(); double _collapseProgress = 0.0; // 定义展开/收缩状态的高度 final double _expandedHeight = 220.0; final double _collapsedHeight = kToolbarHeight; @override void initState() { super.initState(); _scrollController.addListener(_onScroll); } void _onScroll() { // 计算折叠进度:(当前偏移量) / (展开高度与收缩高度的差值) final double scrollOffset = _scrollController.offset; final double maxScrollOffset = _expandedHeight - _collapsedHeight; setState(() { _collapseProgress = (scrollOffset / maxScrollOffset).clamp(0.0, 1.0); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( controller: _scrollController, slivers: [ SliverAppBar( pinned: true, floating: false, expandedHeight: _expandedHeight, toolbarHeight: _collapsedHeight, backgroundColor: Colors.blue, // 自定义flexibleSpace实现过渡效果 flexibleSpace: FlexibleSpaceBar( background: Stack( fit: StackFit.expand, children: [ // 展开时的背景(可选) Container(color: Colors.blue.shade300), // 动态调整的内容区域 Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 大标题:展开时显示,收缩时缩小并移到顶部 AnimatedBuilder( animation: _scrollController, builder: (context, child) { return Transform.translate( offset: Offset(0, -_collapseProgress * 80), child: Opacity( opacity: 1 - _collapseProgress, child: Text( 'Formulia', style: TextStyle( fontSize: 32 - _collapseProgress * 14, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ); }, ), // 副标题/额外内容:收缩时隐藏 AnimatedOpacity( opacity: 1 - _collapseProgress, duration: Duration.zero, // 跟随滚动实时变化 child: const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Text( '专业数学工具集', style: TextStyle(color: Colors.white70), ), ), ), // 搜索栏:收缩时缩小并移到导航栏内 AnimatedContainer( duration: Duration.zero, height: 40 - _collapseProgress * 8, margin: EdgeInsets.only(bottom: 16 - _collapseProgress * 16), child: TextField( decoration: InputDecoration( filled: true, fillColor: Colors.white.withOpacity(0.9), hintText: '搜索公式...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), ), ), ], ), ), // 收缩时的导航栏标题(默认隐藏,折叠到一定程度显示) Align( alignment: Alignment.topCenter, child: Opacity( opacity: _collapseProgress, child: SizedBox( height: _collapsedHeight, child: const Center( child: Text( 'Formulia', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ), ), ], ), ), // 导航栏操作按钮 actions: const [ Padding( padding: EdgeInsets.only(right: 16.0), child: Icon(Icons.more_vert), ), ], ), // 主体内容 SliverList( delegate: SliverChildBuilderDelegate( (context, index) => ListTile( title: Text('列表项 $index'), subtitle: Text('这是第${index+1}个内容项'), ), childCount: 50, ), ), ], ), ); } }
关键细节说明
- 折叠进度计算:通过
scrollOffset / (_expandedHeight - _collapsedHeight)得到0到1的比例,控制所有元素的过渡 - AnimatedBuilder/AnimatedOpacity/AnimatedContainer:利用这些组件实现滚动时的实时UI变化,避免手动 setState 带来的性能问题
- Stack布局:同时承载展开状态的内容和收缩状态的导航栏标题,通过透明度切换显示
- 边界处理:用
clamp(0.0,1.0)确保进度值不会超出0-1范围,避免UI异常
你可以根据实际需求调整_expandedHeight、_collapsedHeight以及各个元素的过渡参数,完全复刻Formulia的效果。
内容的提问来源于stack exchange,提问作者user21766581
相关产品推荐
相关产品推荐

