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

Widget添加与移除时的缩放动画实现咨询

Flutter Stack组件中Widget的添加/移除缩放动画实现

核心实现思路

通过给Stack中的每个Widget包裹ScaleTransition,结合AnimationController单独控制每个项的缩放动画:

  • 添加Widget时:初始化动画控制器,执行从0到1的缩放过渡,实现"从无到有"的效果
  • 移除Widget时:先执行从1到0的缩放过渡,动画完成后再将Widget从Stack的子列表中移除

完整代码示例

import 'package:flutter/material.dart';

class AnimatedStackDemo extends StatefulWidget {
  const AnimatedStackDemo({super.key});

  @override
  State<AnimatedStackDemo> createState() => _AnimatedStackDemoState();
}

class _AnimatedStackDemoState extends State<AnimatedStackDemo> with TickerProviderStateMixin {
  // 维护Stack中的子项,每个项包含Widget和对应的动画控制器
  final List<_AnimatedStackItem> _stackItems = [];
  int _itemCounter = 0;

  // 添加新Widget到Stack
  void _addItem() {
    setState(() {
      final controller = AnimationController(
        duration: const Duration(milliseconds: 300),
        vsync: this,
      );
      final scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(parent: controller, curve: Curves.easeOut),
      );
      _stackItems.add(_AnimatedStackItem(
        key: UniqueKey(),
        child: Container(
          key: ValueKey(_itemCounter),
          width: 100,
          height: 100,
          color: Colors.primaries[_itemCounter % Colors.primaries.length],
          child: Center(child: Text('Item ${_itemCounter + 1}')),
        ),
        controller: controller,
        scaleAnimation: scaleAnimation,
      ));
      _itemCounter++;
      // 启动放大动画
      controller.forward();
    });
  }

  // 移除最后一个Widget
  void _removeItem() {
    if (_stackItems.isEmpty) return;
    final lastItem = _stackItems.last;
    // 启动缩小动画,完成后移除并释放控制器
    lastItem.controller.reverse().then((_) {
      setState(() {
        _stackItems.remove(lastItem);
        lastItem.controller.dispose();
      });
    });
  }

  @override
  void dispose() {
    // 释放所有剩余的动画控制器
    for (final item in _stackItems) {
      item.controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Animated Stack Demo')),
      body: Stack(
        alignment: Alignment.center,
        children: _stackItems.map((item) {
          return ScaleTransition(
            key: item.key,
            scale: item.scaleAnimation,
            child: item.child,
          );
        }).toList(),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _addItem,
            child: const Icon(Icons.add),
          ),
          const SizedBox(width: 10),
          FloatingActionButton(
            onPressed: _removeItem,
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

// 封装Stack项的信息
class _AnimatedStackItem {
  final Key key;
  final Widget child;
  final AnimationController controller;
  final Animation<double> scaleAnimation;

  _AnimatedStackItem({
    required this.key,
    required this.child,
    required this.controller,
    required this.scaleAnimation,
  });
}

关键细节说明

  • 动画控制器管理:每个Stack项对应独立的AnimationController,确保添加/移除动画互不干扰,动画结束后及时释放控制器避免内存泄漏
  • 缩放动画曲线:示例中使用Curves.easeOut让放大动画更自然,你可以根据需求替换为其他曲线(如Curves.bounceOut)
  • Stack布局:示例中用Alignment.center让所有项居中叠加,你可以根据需要调整每个项的位置(比如给每个Container添加Positioned组件)
  • 唯一Key:给每个ScaleTransition和子Widget设置唯一Key,帮助Flutter识别Widget的添加/移除动作,避免动画异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 19:25:27