Jetpack Compose:如何实现面板的平滑部分折叠效果?
优化三状态面板的联动滑动动画方案
核心思路:放弃独立组件的AnimatedVisibility,改用整体位置/高度动画实现区域联动
方案1:AnimatedContainer + 固定高度控制(适合高度已知的场景)
通过AnimatedContainer动态修改面板总高度,让三个区域随整体高度变化同步滑动,避免单独组件动画的断层问题:
enum PanelState { collapsed, fullyExpanded, partiallyExpanded } class TriStatePanel extends StatefulWidget { @override _TriStatePanelState createState() => _TriStatePanelState(); } class _TriStatePanelState extends State<TriStatePanel> { PanelState _currentState = PanelState.collapsed; final double _topAreaHeight = 100; final double _middleAreaHeight = 150; final double _bottomAreaHeight = 200; double get _panelTotalHeight { switch (_currentState) { case PanelState.collapsed: return 0; case PanelState.fullyExpanded: return _topAreaHeight + _middleAreaHeight + _bottomAreaHeight; case PanelState.partiallyExpanded: return _middleAreaHeight; } } void _switchPanelState() { setState(() { switch (_currentState) { case PanelState.collapsed: _currentState = PanelState.fullyExpanded; break; case PanelState.fullyExpanded: _currentState = PanelState.partiallyExpanded; break; case PanelState.partiallyExpanded: _currentState = PanelState.fullyExpanded; break; } }); } @override Widget build(BuildContext context) { return Column( children: [ // 按钮栏 ElevatedButton(onPressed: _switchPanelState, child: Text('切换面板状态')), Expanded( child: Align( alignment: Alignment.bottomCenter, child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, height: _panelTotalHeight, width: double.infinity, child: Column( children: [ Container(height: _topAreaHeight, color: Colors.blue), Container(height: _middleAreaHeight, color: Colors.green), Container(height: _bottomAreaHeight, color: Colors.orange), ], ), ), ), ), ], ); } }
方案2:Stack + AnimatedPositioned 实现精细位置联动
将三个区域放入Stack,通过AnimatedPositioned控制整体垂直偏移,同时配合透明度动画处理顶部区域的隐藏,实现更贴近需求的"顶部区域隐藏在中间区域后方"效果:
class TriStatePanel extends StatefulWidget { @override _TriStatePanelState createState() => _TriStatePanelState(); } class _TriStatePanelState extends State<TriStatePanel> { PanelState _currentState = PanelState.collapsed; final double _topH = 100; final double _middleH = 150; final double _bottomH = 200; double get _panelOffsetY { switch (_currentState) { case PanelState.collapsed: return _middleH + _bottomH; case PanelState.fullyExpanded: return 0; case PanelState.partiallyExpanded: return _topH; } } double get _topAreaOpacity => _currentState == PanelState.partiallyExpanded ? 0 : 1; double get _bottomAreaOpacity => _currentState == PanelState.partiallyExpanded ? 0 : 1; void _switchState() { setState(() { switch (_currentState) { case PanelState.collapsed: _currentState = PanelState.fullyExpanded; break; case PanelState.fullyExpanded: _currentState = PanelState.partiallyExpanded; break; case PanelState.partiallyExpanded: _currentState = PanelState.fullyExpanded; break; } }); } @override Widget build(BuildContext context) { return Column( children: [ ElevatedButton(onPressed: _switchState, child: Text('切换状态')), Expanded( child: Stack( alignment: Alignment.bottomCenter, children: [ // 底部区域 AnimatedOpacity( duration: const Duration(milliseconds: 300), opacity: _bottomAreaOpacity, child: Container(height: _bottomH, color: Colors.orange), ), // 中间区域 AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, bottom: _panelOffsetY, left: 0, right: 0, child: Container(height: _middleH, color: Colors.green), ), // 顶部区域 AnimatedOpacity( duration: const Duration(milliseconds: 300), opacity: _topAreaOpacity, child: AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, bottom: _panelOffsetY + _middleH, left: 0, right: 0, child: Container(height: _topH, color: Colors.blue), ), ), ], ), ), ], ); } }
关键优化说明
- 抛弃
AnimatedVisibility的独立组件动画逻辑,改用整体高度/位置变化驱动所有区域同步运动,彻底解决动画衔接断层问题 - 用枚举统一管理面板状态,确保状态转换逻辑严谨,避免出现异常跳转
- 统一使用
Curves.easeInOut动画曲线,让滑动过渡更符合自然交互体验
内容的提问来源于stack exchange,提问作者CommonsWare
相关产品推荐
相关产品推荐

