You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter问题:如何将组件高度动画恢复至原始内容高度

解决AnimatedContainer动画恢复到内容默认高度的问题

核心问题在于:AnimatedContainer的隐式动画需要明确的数值起点和终点,直接设置height: null会跳过动画逻辑(直接让子组件决定高度),同时还会触发非空类型错误。要实现动画过渡到内容高度,得先获取内容的实际高度,再基于这个数值做动画。

方案一:GlobalKey获取内容高度 + 隐式动画

先通过GlobalKey拿到子组件的实际高度,再让AnimatedContainer动画到这个值:

GlobalKey _contentKey = GlobalKey();
double? height = 100;

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: GestureDetector(
      onTap: () {
        setState(() {
          if (height == 100) {
            // 获取子组件的实际高度
            final renderBox = _contentKey.currentContext?.findRenderObject() as RenderBox;
            height = renderBox.size.height;
          } else {
            height = 100;
          }
        });
      },
      child: Center(
        child: AnimatedContainer(
          curve: Curves.bounceOut,
          duration: const Duration(milliseconds: 500),
          width: 100,
          height: height,
          color: Colors.deepPurple,
          child: Text(
            'This text set to be much larger than 100 aaaaaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaa.',
            style: TextStyle(fontSize: 30, color: Colors.white),
            key: _contentKey,
          ),
        ),
      ),
    ),
  );
}

说明:

  • 用GlobalKey绑定子组件,点击时通过RenderBox获取其实际高度
  • 切换时要么回到固定高度100,要么动画到内容高度,全程都是数值过渡,符合隐式动画的要求

方案二:显式动画(AnimationController + AnimatedBuilder)

如果需要更精细的控制,比如自定义动画曲线、监听动画状态,可以用显式动画:

final _controller = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);
late Animation<double> _heightAnimation;
GlobalKey _contentKey = GlobalKey();
bool _isCollapsed = true;
double _contentHeight = 0;

@override
void initState() {
  super.initState();
  // 先获取内容高度(build完成后执行)
  WidgetsBinding.instance.addPostFrameCallback((_) {
    final renderBox = _contentKey.currentContext?.findRenderObject() as RenderBox;
    _contentHeight = renderBox.size.height;
    _heightAnimation = Tween<double>(begin: 100, end: _contentHeight).animate(
      CurvedAnimation(parent: _controller, curve: Curves.bounceOut),
    );
  });
}

@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: GestureDetector(
      onTap: () {
        if (_isCollapsed) {
          _controller.forward();
        } else {
          _controller.reverse();
        }
        _isCollapsed = !_isCollapsed;
      },
      child: Center(
        child: AnimatedBuilder(
          animation: _heightAnimation,
          builder: (context, child) {
            return Container(
              width: 100,
              height: _heightAnimation.value,
              color: Colors.deepPurple,
              child: child,
            );
          },
          child: Text(
            'This text set to be much larger than 100 aaaaaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaa.',
            style: TextStyle(fontSize: 30, color: Colors.white),
            key: _contentKey,
          ),
        ),
      ),
    ),
  );
}

说明:

  • 用AnimationController控制动画的播放/反向
  • 初始化时通过addPostFrameCallback获取内容的实际高度,创建对应的Tween动画
  • 点击时触发控制器的forward/reverse,实现展开/折叠的动画过渡

为什么直接用null不行?

当AnimatedContainer的height设为null时,它会直接采用子组件的尺寸布局,不会生成中间的动画过渡值——隐式动画的核心是基于数值变化生成过渡帧,null不属于可插值的数值类型,所以无法触发动画。必须先拿到内容的具体高度数值,才能让动画系统生成过渡效果。

内容的提问来源于stack exchange,提问作者enchance

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 10:05:24