Flutter双ExpansionTile布局问题:展开时另一个需固定在容器底部
解决方案
要实现一个ExpansionTile展开时另一个固定在SizedBox底部,收起时回到正常排列的效果,你需要通过状态跟踪动态调整布局结构。以下是两种可行的实现方案:
方案一:基于Column的简洁实现(推荐)
这种方案通过StatefulWidget跟踪两个ExpansionTile的展开状态,动态切换是否使用Expanded包裹展开的面板,让未展开的面板自动固定在底部:
class DualExpansionTiles extends StatefulWidget { final SuccessState successState; const DualExpansionTiles({super.key, required this.successState}); @override State<DualExpansionTiles> createState() => _DualExpansionTilesState(); } class _DualExpansionTilesState extends State<DualExpansionTiles> { bool _devExpanded = false; bool _testerExpanded = false; @override Widget build(BuildContext context) { return SizedBox( height: 500, child: Column( children: [ // 开发者面板:展开时占满剩余空间,收起时仅占自身高度 _devExpanded ? Expanded( child: SingleChildScrollView( child: ExpansionTile( title: const Text('developer'), initiallyExpanded: true, onExpansionChanged: (value) { setState(() { _devExpanded = value; // 可选:展开一个时自动收起另一个 if (value) _testerExpanded = false; }); }, children: List.generate( widget.successState.developerList.length, (index) => EmployeeTileWidget( employee: widget.successState.developerList[index]), ), ), ), ) : ExpansionTile( title: const Text('developer'), onExpansionChanged: (value) { setState(() { _devExpanded = value; if (value) _testerExpanded = false; }); }, children: List.generate( widget.successState.developerList.length, (index) => EmployeeTileWidget( employee: widget.successState.developerList[index]), ), ), // 两个面板都收起时的间距,还原正常布局 if (!_devExpanded && !_testerExpanded) const SizedBox(height: 16), // 测试者面板:展开时占满剩余空间,收起时固定在底部 _testerExpanded ? Expanded( child: SingleChildScrollView( child: ExpansionTile( title: const Text('testerList'), initiallyExpanded: true, onExpansionChanged: (value) { setState(() { _testerExpanded = value; if (value) _devExpanded = false; }); }, children: List.generate( widget.successState.testerList.length, (index) => EmployeeTileWidget( employee: widget.successState.testerList[index]), ), ), ), ) : ExpansionTile( title: const Text('testerList'), onExpansionChanged: (value) { setState(() { _testerExpanded = value; if (value) _devExpanded = false; }); }, children: List.generate( widget.successState.testerList.length, (index) => EmployeeTileWidget( employee: widget.successState.testerList[index]), ), ), ], ), ); } }
方案思路
- 用
_devExpanded和_testerExpanded两个布尔值跟踪面板的展开状态 - 当某个面板展开时,用
Expanded包裹它,使其占据SizedBox内的剩余空间,内部通过SingleChildScrollView处理过长内容的滚动 - 未展开的面板保持自身高度,在Column布局中会被Expanded组件推到底部,实现固定效果
- 两个面板都收起时,添加间距还原正常的上下排列布局
方案二:基于Stack的精准控制
如果需要更精确地控制面板位置(比如展开时未激活的面板始终固定在边缘),可以使用Stack布局:
class DualExpansionTiles extends StatefulWidget { final SuccessState successState; const DualExpansionTiles({super.key, required this.successState}); @override State<DualExpansionTiles> createState() => _DualExpansionTilesState(); } class _DualExpansionTilesState extends State<DualExpansionTiles> { bool _devExpanded = false; bool _testerExpanded = false; @override Widget build(BuildContext context) { return SizedBox( height: 500, child: Stack( children: [ // 展开的面板:占满整个SizedBox空间,内部滚动 if (_devExpanded) Positioned.fill( child: SingleChildScrollView( child: ExpansionTile( title: const Text('developer'), initiallyExpanded: true, onExpansionChanged: (value) => setState(() => _devExpanded = value), children: List.generate( widget.successState.developerList.length, (index) => EmployeeTileWidget( employee: widget.successState.developerList[index]), ), ), ), ), if (_testerExpanded) Positioned.fill( child: SingleChildScrollView( child: ExpansionTile( title: const Text('testerList'), initiallyExpanded: true, onExpansionChanged: (value) => setState(() => _testerExpanded = value), children: List.generate( widget.successState.testerList.length, (index) => EmployeeTileWidget( employee: widget.successState.testerList[index]), ), ), ), ), // 未展开状态:正常上下排列 if (!_devExpanded && !_testerExpanded) Column( children: [ ExpansionTile( title: const Text('developer'), onExpansionChanged: (value) { setState(() { _devExpanded = value; _testerExpanded = false; }); }, children: List.generate( widget.successState.developerList.length, (index) => EmployeeTileWidget( employee: widget.successState.developerList[index]), ), ), const SizedBox(height: 16), ExpansionTile( title: const Text('testerList'), onExpansionChanged: (value) { setState(() { _testerExpanded = value; _devExpanded = false; }); }, children: List.generate( widget.successState.testerList.length, (index) => EmployeeTileWidget( employee: widget.successState.testerList[index]), ), ), ], ), // 开发者面板展开时,测试面板固定在底部 if (_devExpanded) Positioned( bottom: 0, left: 0, right: 0, child: ExpansionTile( title: const Text('testerList'), onExpansionChanged: (value) { setState(() { _testerExpanded = value; _devExpanded = false; }); }, children: List.generate( widget.successState.testerList.length, (index) => EmployeeTileWidget( employee: widget.successState.testerList[index]), ), ), ), // 测试面板展开时,开发者面板固定在顶部 if (_testerExpanded) Positioned( top: 0, left: 0, right: 0, child: ExpansionTile( title: const Text('developer'), onExpansionChanged: (value) { setState(() { _devExpanded = value; _testerExpanded = false; }); }, children: List.generate( widget.successState.developerList.length, (index) => EmployeeTileWidget( employee: widget.successState.developerList[index]), ), ), ), ], ), ); } }
方案思路
- 用Stack分层管理面板:展开的面板在底层占满空间,未展开的面板在顶层固定位置
- 通过
Positioned精确控制未展开面板的位置(顶部或底部) - 未展开状态下回退到Column布局,保持正常排列
内容的提问来源于stack exchange,提问作者Sanwar Jayswal
相关产品推荐
相关产品推荐

