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

Flutter:表单提交验证通过后带动画移除屏幕中的卡片

表单卡片动画移除的最佳实现方式

你当前用Future.delayed(Duration.zero)获取尺寸的方法能实现效果,但有明显缺陷:比如布局变动(屏幕旋转、父组件尺寸变化)时,预先获取的宽高不会同步更新,可能导致动画错位;而且只是把尺寸缩到0,卡片实际还留在组件树里,不够优雅。下面是几种更可靠的实现方案:

方案一:AnimatedContainer + 尺寸获取 + 动画后移除组件

这种方式保留你获取尺寸的思路,但优化动画逻辑,确保动画结束后彻底移除组件:

class FormCard extends StatefulWidget {
  @override
  _FormCardState createState() => _FormCardState();
}

class _FormCardState extends State<FormCard> {
  final _formKey = GlobalKey<FormState>();
  final _cardKey = GlobalKey();
  double? _cardHeight;
  double? _cardWidth;
  bool _isRemoving = false;
  bool _isRemoved = false;

  @override
  void initState() {
    super.initState();
    // 组件渲染完成后获取初始尺寸
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final size = _cardKey.currentContext?.size;
      if (size != null) {
        setState(() {
          _cardHeight = size.height;
          _cardWidth = size.width;
        });
      }
    });
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      setState(() => _isRemoving = true);
      // 动画结束后标记组件为已移除
      Future.delayed(const Duration(milliseconds: 300), () {
        setState(() => _isRemoved = true);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isRemoved) return const SizedBox.shrink();

    return AnimatedContainer(
      key: _cardKey,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOut,
      width: _isRemoving ? 0 : _cardWidth,
      height: _isRemoving ? 0 : _cardHeight,
      opacity: _isRemoving ? 0 : 1,
      child: Card(
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              // 表单字段
              TextFormField(validator: (val) => val?.isEmpty ?? true ? '必填' : null),
              ElevatedButton(onPressed: _submitForm, child: const Text('提交')),
            ],
          ),
        ),
      ),
    );
  }
}

这里用WidgetsBinding.instance.addPostFrameCallback替代Future.delayed(Duration.zero),更可靠地获取组件渲染后的尺寸;动画结束后通过_isRemoved状态把卡片替换为空组件,彻底从组件树移除。

方案二:SizeTransition + AnimatedOpacity 组合(无需手动获取尺寸)

这个方案不需要手动获取卡片尺寸,利用Flutter内置的过渡组件自动处理缩放和淡入淡出:

class FormCard extends StatefulWidget {
  @override
  _FormCardState createState() => _FormCardState();
}

class _FormCardState extends State<FormCard> with SingleTickerProviderStateMixin {
  final _formKey = GlobalKey<FormState>();
  late AnimationController _animationController;
  bool _isRemoved = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      _animationController.forward().then((_) {
        setState(() => _isRemoved = true);
      });
    }
  }

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

  @override
  Widget build(BuildContext context) {
    if (_isRemoved) return const SizedBox.shrink();

    return AnimatedOpacity(
      opacity: _animationController.isCompleted ? 0 : 1 - _animationController.value,
      duration: const Duration(milliseconds: 300),
      child: SizeTransition(
        sizeFactor: CurvedAnimation(
          parent: _animationController,
          curve: Curves.easeInOut,
        ),
        axisAlignment: 0.5,
        child: Card(
          child: Form(
            key: _formKey,
            child: Column(
              children: [
                TextFormField(validator: (val) => val?.isEmpty ?? true ? '必填' : null),
                ElevatedButton(onPressed: _submitForm, child: const Text('提交')),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

SizeTransition会基于卡片的固有尺寸自动计算缩放比例,配合AnimatedOpacity实现淡入淡出,不需要手动管理宽高,代码更简洁,适配性更强。

方案三:AnimatedSwitcher 一键切换动画

如果想更简单,用AnimatedSwitcher可以快速实现组件切换时的动画效果,不需要手动管理动画控制器:

class FormCard extends StatefulWidget {
  @override
  _FormCardState createState() => _FormCardState();
}

class _FormCardState extends State<FormCard> {
  final _formKey = GlobalKey<FormState>();
  bool _showCard = true;

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      setState(() => _showCard = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(
      duration: const Duration(milliseconds: 300),
      transitionBuilder: (child, animation) {
        return ScaleTransition(
          scale: animation,
          child: FadeTransition(
            opacity: animation,
            child: child,
          ),
        );
      },
      child: _showCard
          ? Card(
              key: const ValueKey('form-card'),
              child: Form(
                key: _formKey,
                child: Column(
                  children: [
                    TextFormField(validator: (val) => val?.isEmpty ?? true ? '必填' : null),
                    ElevatedButton(onPressed: _submitForm, child: const Text('提交')),
                  ],
                ),
              ),
            )
          : const SizedBox.shrink(key: ValueKey('empty')),
    );
  }
}

只需切换_showCard状态,AnimatedSwitcher就会自动执行你定义的缩放+淡入淡出动画,代码量最少,适合快速实现需求。

内容的提问来源于stack exchange,提问作者Mert Doğruca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 03:45:43