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

Flutter:如何为Flex内组件的展开与普通状态添加过渡动画

Flex组件内Expanded状态切换的平滑动画实现

需求

在Flex(如Column/Row)组件内,实现子组件从Expanded状态切换为普通状态时的尺寸动画过渡,同时让Flex的其他子组件也平滑过渡到新位置。

当前问题

现有代码切换状态时尺寸直接跳转,无动画效果:

class MyHomePage extends Stateful Widget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool expand = false;

  void _expandToggle() {
    setState(() => expand = !expand);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Whether the child below is expanded:',
            ),
            Expanded(
              flex: expand ? 1 : 0,
              child: AnimatedSize(
                duration: const Duration(seconds: 3),
                child: ColoredBox(
                  color: Colors.yellow,
                  child: Text(
                    '$expand',
                    style: Theme.of(context).textTheme.headlineMedium,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _expandToggle,
        child: const Icon(Icons.add),
      ),
    );
  }
}

问题原因

Expanded是Flex布局的约束组件,必须直接作为Flex的子组件,无法被AnimatedSize包裹——这也是直接嵌套无效的核心原因。

解决方案

方案一:AnimatedContainer + Flexible

通过Flexible让容器获取剩余空间权限,结合AnimatedContainer动态修改高度(Row场景改宽度)实现动画:

class MyHomePage extends Stateful Widget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool expand = false;

  void _expandToggle() {
    setState(() => expand = !expand);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Whether the child below is expanded:',
            ),
            Flexible(
              child: AnimatedContainer(
                duration: const Duration(seconds: 3),
                curve: Curves.easeInOut,
                // 展开时占满剩余空间,收起时高度归0
                height: expand ? double.infinity : 0,
                child: ColoredBox(
                  color: Colors.yellow,
                  child: Text(
                    '$expand',
                    style: Theme.of(context).textTheme.headlineMedium,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _expandToggle,
        child: const Icon(Icons.add),
      ),
    );
  }
}

方案二:AnimatedCrossFade

用AnimatedCrossFade在收起状态的空容器和展开状态的Expanded组件之间做过渡动画:

class MyHomePage extends Stateful Widget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool expand = false;

  void _expandToggle() {
    setState(() => expand = !expand);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Whether the child below is expanded:',
            ),
            AnimatedCrossFade(
              duration: const Duration(seconds: 3),
              sizeCurve: Curves.easeInOut,
              // 收起状态:高度为0的空容器
              firstChild: const SizedBox(height: 0),
              // 展开状态:带Expanded的目标组件
              secondChild: Expanded(
                child: ColoredBox(
                  color: Colors.yellow,
                  child: Text(
                    '$expand',
                    style: Theme.of(context).textTheme.headlineMedium,
                  ),
                ),
              ),
              // 根据状态切换显示的组件
              crossFadeState: expand ? CrossFadeState.showSecond : CrossFadeState.showFirst,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _expandToggle,
        child: const Icon(Icons.add),
      ),
    );
  }
}

方案说明

  • 方案一:适合需要精确控制尺寸变化过程的场景,Flexible保证容器能利用剩余空间,AnimatedContainer自动处理尺寸动画,其他子组件会随布局变化平滑移动。
  • 方案二:适合需要同时加入淡入淡出效果的场景,通过组件切换实现过渡,代码更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 16:24:52