Flutter中如何让AnimatedContainer高度自适应子内容并实现动画
Flutter 自适应内容高度的展开/收起动画实现
AnimatedContainer 本身只支持在两个明确的数值之间做动画插值,拿不到起止高度差的时候自然没法触发过渡,针对子内容高度动态变化、无法提前写死固定值的场景,直接用下面几种方案即可,不需要硬编码高度。
方案1:用AnimatedSize实现(最简便,零高度计算)
这是适配动态内容成本最低的方案,AnimatedSize 会自动监听子组件的尺寸变化,只要子组件的实际高度发生改变,就会自动执行过渡动画,完全不需要手动计算内容高度。
核心逻辑:
- 收起状态下直接渲染高度为0的空容器
- 展开状态下渲染实际内容组件
- 给容器加上裁剪属性,避免动画过程中内容溢出
修改后的可运行代码如下:
import 'package:flutter/material.dart'; class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { bool boolValue = false; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Container( color: Colors.blue, child: AnimatedSize( duration: const Duration(milliseconds: 500), curve: Curves.easeInOut, clipBehavior: Clip.hardEdge, child: boolValue ? const SizedBox(height: 0) : SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: const [ Text('Hello world , Hello Dart',style: TextStyle(color: Colors.white,fontSize: 15),), Text('Hello world , Hello Dart',style: TextStyle(color: Colors.white,fontSize: 15),), Text('Hello world , Hello Dart',style: TextStyle(color: Colors.white,fontSize: 15),), Text('Hello world , Hello Dart',style: TextStyle(color: Colors.white,fontSize: 15),), Text('Hello world , Hello Dart',style: TextStyle(color: Colors.white,fontSize: 15),), ], ), ), ), ) ), floatingActionButton: FloatingActionButton( backgroundColor: const Color(0xFFE57373), onPressed: (){ setState(() {boolValue = !boolValue;}); }, child: const Icon(Icons.check) ) ); } }
后续在Column里动态增减子项、修改文本内容导致高度变化,动画都会自动适配,不需要额外加监听逻辑。
方案2:手动测量子组件高度(适合需要自定义动画参数的场景)
如果需要更灵活的控制(比如结合其他属性联动动画),可以通过GlobalKey手动测量子组件渲染后的实际高度,动态传给AnimatedContainer:
- 给内容组件绑定
GlobalKey - 组件首次渲染、内容更新后,通过key拿到渲染对象的
size属性,获取实际内容高度存为状态值 - 展开时把测量到的高度传给
AnimatedContainer,收起时传0即可
核心代码片段参考:
// 声明key和高度状态 final GlobalKey _contentKey = GlobalKey(); double _contentHeight = 0; // 初始化、内容更新时调用测量逻辑 void _measureContentHeight() { WidgetsBinding.instance.addPostFrameCallback((_) { final renderBox = _contentKey.currentContext?.findRenderObject() as RenderBox?; if (renderBox != null) { setState(() { _contentHeight = renderBox.size.height; }); } }); } // 传给AnimatedContainer AnimatedContainer( duration: const Duration(milliseconds: 500), color: Colors.blue, height: boolValue ? 0 : _contentHeight, child: SingleChildScrollView( key: _contentKey, // 绑定key child: Column(/* 动态内容 */), ), )
这个方案的缺点是需要自己监听内容变化触发重新测量,维护成本比AnimatedSize高,非必要不选。
方案3:内置组件ExpansionTile(符合Material设计的列表场景)
如果展开组件是带标题的列表项、符合Material设计规范,直接用官方内置的ExpansionTile即可,它内部已经封装好了自适应内容高度的展开动画,支持自定义图标、背景色、动画时长,不需要自己写动画逻辑。
注意事项
- 所有折叠动画场景务必给容器设置
clipBehavior: Clip.hardEdge,否则动画过程中超出容器的内容会直接显示在外面,出现视觉异常 - 子内容高度动态变化的场景优先选
AnimatedSize方案,不需要手动维护高度状态,代码鲁棒性最高
内容的提问来源于stack exchange,提问作者Mohammed Hamdan
相关产品推荐
相关产品推荐

