Flutter中如何实现非SliverAppBar的下拉展开式AppBar?
Flutter 下拉展开式 AppBar 实现方案
核心思路
放弃SliverAppBar的滚动联动逻辑,自定义可切换高度的AppBar组件:
- 初始为收起的常规高度,仅显示核心导航元素
- 支持下拉手势或点击触发,通过动画展开隐藏内容
- 展开时AppBar悬浮在页面内容上方,不干扰下方内容布局
完整代码实现
import 'package:flutter/material.dart'; class ExpandableAppBarPage extends StatefulWidget { const ExpandableAppBarPage({super.key}); @override State<ExpandableAppBarPage> createState() => _ExpandableAppBarPageState(); } class _ExpandableAppBarPageState extends State<ExpandableAppBarPage> with SingleTickerProviderStateMixin { bool _isExpanded = false; late AnimationController _animationController; late Animation<double> _heightAnimation; // 定义收起/展开状态的高度 final double _collapsedHeight = 60; final double _expandedHeight = 200; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _heightAnimation = Tween<double>( begin: _collapsedHeight, end: _expandedHeight, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } @override void dispose() { _animationController.dispose(); super.dispose(); } // 切换展开/收起状态 void _toggleExpansion() { setState(() { _isExpanded = !_isExpanded; _isExpanded ? _animationController.forward() : _animationController.reverse(); }); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 页面主体内容 Positioned( top: _collapsedHeight, left: 0, right: 0, bottom: 0, child: ListView.builder( padding: EdgeInsets.zero, itemCount: 20, itemBuilder: (context, index) => ListTile(title: Text('列表项 $index')), ), ), // 可展开的AppBar GestureDetector( onVerticalDragUpdate: (details) { // 下拉超过20像素触发展开 if (details.delta.dy > 2 && !_isExpanded) _toggleExpansion(); // 上拉超过20像素触发收起 else if (details.delta.dy < -2 && _isExpanded) _toggleExpansion(); }, onTap: _toggleExpansion, // 点击也可切换状态 child: AnimatedBuilder( animation: _heightAnimation, builder: (context, child) { return Container( height: _heightAnimation.value, color: Theme.of(context).primaryColor, padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 收起状态的导航栏内容 SizedBox( height: _collapsedHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '首页', style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), ), IconButton( icon: Icon(_isExpanded ? Icons.expand_less : Icons.expand_more, color: Colors.white), onPressed: _toggleExpansion, ), ], ), ), // 展开状态的隐藏内容 if (_isExpanded) Expanded( child: Padding( padding: const EdgeInsets.only(top: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text('隐藏内容1', style: TextStyle(color: Colors.white70)), SizedBox(height: 8), Text('隐藏内容2', style: TextStyle(color: Colors.white70)), SizedBox(height: 8), Text('隐藏内容3', style: TextStyle(color: Colors.white70)), ], ), ), ), ], ), ); }, ), ), ], ), ); } }
关键细节说明
- 平滑动画:通过
AnimationController和Tween实现高度过渡,避免生硬的尺寸变化 - 手势兼容:同时支持拖拽和点击触发,适配不同操作习惯
- 层级布局:用
Stack将AppBar置于内容上方,主体内容固定在收起高度下方,展开时AppBar覆盖内容 - 状态同步:通过
_isExpanded变量统一控制动画和内容显示逻辑
内容的提问来源于stack exchange,提问作者Rudransh Singh Mahra
相关产品推荐
相关产品推荐

