Flutter使用ExpansionWidget实现展开新项时自动收起已展开折叠项
Flutter 手风琴折叠效果实现方案
核心需求
现有基于expansion_widget库封装的CustomExpansionTile组件,多组件放在Column中使用时,需要实现手风琴交互:每次仅允许一个折叠项处于展开状态,展开新项时自动收起之前展开的项。
改造步骤
1. 改造CustomExpansionTile组件
增加状态管控相关参数,将展开状态的控制权交给父组件,改造后代码如下:
import 'package:expansion_widget/expansion_widget.dart'; import 'package:flutter/material.dart'; class CustomExpansionTile extends StatefulWidget { final Widget headerBody; final Widget expandedBody; final Color headerColor; final Color expandedBodyColor; final double padding; // 新增参数:当前组件的唯一标识 final dynamic id; // 新增参数:当前处于展开状态的组件标识 final dynamic currentActiveId; // 新增参数:状态变更回调 final Function(dynamic?) onToggle; const CustomExpansionTile({ Key? key, required this.headerBody, required this.expandedBody, required this.headerColor, required this.expandedBodyColor, required this.padding, required this.id, required this.currentActiveId, required this.onToggle, }) : super(key: key); @override _CustomExpansionTileState createState() => _CustomExpansionTileState(); } class _CustomExpansionTileState extends State<CustomExpansionTile> { // 保存ExpansionWidget的toggle方法用于手动控制展开收起 Function? _toggleFunction; bool _isExpanded = false; @override void didUpdateWidget(covariant CustomExpansionTile oldWidget) { super.didUpdateWidget(oldWidget); // 父组件传来的激活标识变化时,同步更新当前组件的展开状态 final shouldExpand = widget.id == widget.currentActiveId; if (shouldExpand != _isExpanded && _toggleFunction != null) { _toggleFunction!(animated: true); _isExpanded = shouldExpand; } } @override Widget build(BuildContext context) { return Column( children: [ Card( elevation: 0, child: ExpansionWidget( initiallyExpanded: widget.id == widget.currentActiveId, titleBuilder: (double animationValue, _, bool isExpaned, toogleFunction) { _toggleFunction = toogleFunction; _isExpanded = isExpaned; return Container( decoration: BoxDecoration( color: widget.headerColor, borderRadius: BorderRadius.circular(6)), height: 59, child: InkWell( onTap: () { // 点击时通知父组件更新激活标识 if (_isExpanded) { widget.onToggle(null); } else { widget.onToggle(widget.id); } }, child: Padding( padding: EdgeInsets.symmetric(horizontal: widget.padding), child: Row( children: [ widget.headerBody, const Spacer(), Transform.rotate( angle: 0, child: Icon( isExpaned ? Icons.keyboard_arrow_down_rounded : Icons.keyboard_arrow_right, size: 40), alignment: Alignment.centerRight, ) ], ), ), ), ); }, content: Container( color: widget.expandedBodyColor, width: double.infinity, padding: const EdgeInsets.all(20), child: Column( children: [widget.expandedBody], ), ), ), ), ], ); } }
2. 父组件调用示例
父组件维护全局激活标识,管控所有折叠项的状态:
class AccordionPage extends StatefulWidget { const AccordionPage({super.key}); @override State<AccordionPage> createState() => _AccordionPageState(); } class _AccordionPageState extends State<AccordionPage> { // 存储当前处于展开状态的折叠项id,null代表所有项都收起 dynamic? _activeTileId; @override Widget build(BuildContext context) { return Scaffold( body: ListView( padding: const EdgeInsets.all(16), children: [ CustomExpansionTile( id: 1, currentActiveId: _activeTileId, onToggle: (id) => setState(() => _activeTileId = id), headerBody: Row( children: const [ Text('第一项标题'), Text('附加文本'), ], ), expandedBody: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text('第一项内容1'), Text('第一项内容2'), ], ), headerColor: Colors.white, expandedBodyColor: Colors.white, padding: 16, ), CustomExpansionTile( id: 2, currentActiveId: _activeTileId, onToggle: (id) => setState(() => _activeTileId = id), headerBody: Row( children: const [ Text('第二项标题'), Text('附加文本'), ], ), expandedBody: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text('第二项内容1'), Text('第二项内容2'), ], ), headerColor: Colors.white, expandedBodyColor: Colors.white, padding: 16, ), CustomExpansionTile( id: 3, currentActiveId: _activeTileId, onToggle: (id) => setState(() => _activeTileId = id), headerBody: Row( children: const [ Text('第三项标题'), Text('附加文本'), ], ), expandedBody: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text('第三项内容1'), Text('第三项内容2'), ], ), headerColor: Colors.white, expandedBodyColor: Colors.white, padding: 16, ), ], ), ); } }
实现说明
- 所有折叠项的展开状态由父组件统一维护,避免每个组件单独管理状态导致的状态不同步问题
- 点击任意折叠项时,仅需要更新父组件的
_activeTileId变量,所有折叠项会自动对比自身id与激活id,同步更新展开/收起状态 - 点击已经展开的折叠项时,会自动收起该折叠项,所有项回到全部收起状态
内容的提问来源于stack exchange,提问作者NavodDinidu
相关产品推荐
相关产品推荐

