Flutter 如何实现点击标题触发列表动画折叠收起的功能?
实现步骤
你只需要给组件增加折叠状态变量+动画控制,修改现有代码的4处即可实现效果,具体操作如下:
1. 转换为Stateful组件
当前承载这个Column的组件需要改成StatefulWidget,因为需要维护折叠状态和动画控制器的生命周期。
2. 定义状态与动画参数
在State类中增加如下代码:
// 折叠状态标识,默认展开 bool _isCollapsed = false; // 动画控制器 late AnimationController _collapseController; // 高度系数动画 late Animation<double> _heightFactor; @override void initState() { super.initState(); // 初始化动画控制器,时长可自定义 _collapseController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); // 定义高度从1(完全展开)到0(完全收起)的动画曲线 _heightFactor = Tween<double>(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _collapseController, curve: Curves.easeInOut), ); } @override void dispose() { // 销毁控制器避免内存泄漏 _collapseController.dispose(); super.dispose(); }
3. 给标题绑定点击事件
修改标题区域GestureDetector的onTap回调:
onTap: () { setState(() { _isCollapsed = !_isCollapsed; _isCollapsed ? _collapseController.forward() : _collapseController.reverse(); }); }
4. 给列表区域加动画包裹
将原来生成列表的Column用SizeTransition组件包裹,实现高度渐变的折叠效果:
SizeTransition( axisAlignment: 1.0, // 从顶部开始收起动画 sizeFactor: _heightFactor, child: Column( // 你原来的List.generate生成列表的代码直接放在这里即可,无需修改 ), )
特殊场景适配
如果你是多分类列表(有多个这种可折叠的Column),需要将_isCollapsed状态定义在你的Category模型类中,不要存在当前组件的State里,避免点击一个标题所有分类都同时折叠。
修改后完整代码参考
Column( children: [ GestureDetector( onTap: () { setState(() { _isCollapsed = !_isCollapsed; _isCollapsed ? _collapseController.forward() : _collapseController.reverse(); }); }, child: Container( color: Theme.of(context).sliderTheme.inactiveTrackColor, width: MediaQuery.of(context).size.width, child: Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Center( child: Text('${category.name}', style: Theme.of(context).textTheme.headline6.copyWith( fontSize: 16, color: Theme.of(context).selectedRowColor)), ), ), ), ), SizeTransition( axisAlignment: 1.0, sizeFactor: _heightFactor, child: Column( children: List.generate(category.items.length, (index) { return Container( decoration: BoxDecoration( border: (index + 1) != category.items.length ? Border( bottom: BorderSide( color: Style.inactiveColorDark.withOpacity(1.0), width: 0.5)) : null), child: Padding( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( width: 12, height: 12, decoration: BoxDecoration( color: Color(int.parse(category.color)), shape: BoxShape.circle), ), SizedBox( width: 12, ), Text('${category.items[index].description}', style: Theme.of(context) .primaryTextTheme .headline1 .copyWith(fontSize: 16)), ], ), SwitchButton( isActive: category.items[index].isAdded, activeColor: Theme.of(context).sliderTheme.activeTrackColor, inactiveColor: Theme.of(context).sliderTheme.inactiveTrackColor, activeCircleColor: Theme.of(context).sliderTheme.activeTickMarkColor, inactiveCircleColor: Theme.of(context).sliderTheme.inactiveTickMarkColor, turnOn: () { ChosenFeeling removingElement = ChosenFeeling( id: 000, isAdded: false, ); // If chosen list is empty if (chosenfeelings.isEmpty) { chosenfeelings.add(ChosenFeeling( isAdded: true, id: category.items[index].id)); } else { // If user tap on switchButton 2 times chosenfeelings.removeWhere((element) { if (element.id != null && element.id == category.items[index].id) { removingElement = element; } return _isNeedToRemoveWhenOn( currentItem: category.items[index], listItem: element, ); }); // If list isn`t empty and chosen item isn`t in list if (category.items[index].id != removingElement.id) { chosenfeelings.add(ChosenFeeling( id: category.items[index].id, isAdded: true, )); } } }, turnOff: () { ChosenFeeling removingElement = ChosenFeeling( id: 000, isAdded: false, ); if (chosenfeelings.isEmpty) { chosenfeelings.add(ChosenFeeling( id: category.items[index].id, isAdded: false, )); } else { // If user tap on switchButton 2 times chosenfeelings.removeWhere((element) { if (element.id != null && element.id == category.items[index].id) { removingElement = element; } return _isNeedToRemoveWhenOff( currentItem: category.items[index], listItem: element, ); }); // If list isn`t empty and chosen item isn`t in list if (category.items[index].id != removingElement.id) { chosenfeelings.add(ChosenFeeling( id: category.items[index].id, isAdded: false, )); } } }, ) ], ), ), ); }), ), ) ], );
内容的提问来源于stack exchange,提问作者fluffix
相关产品推荐
相关产品推荐

