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

Flutter中FadeInUp动画在TextField交互时重复触发问题

animate_do FadeInUp动画在TextField切换焦点时重复触发的解决方案

问题

使用animate_do: ^3.1.2库为TextField添加FadeInUp动画时,切换输入框焦点会导致动画重复执行,但输入的文本会保留。相同代码在其他场景下无此问题。

原因

  1. Flutter中,若Widget无唯一标识(Key),重建时会被视为新Widget,animate_do的动画组件会重新初始化并执行动画。切换TextField焦点时,若父组件或InputField发生重建,未加Key的FadeInUp会重复触发动画。
  2. InputField的build方法中直接调用MediaQuery.of(context).size.width,键盘弹出/收起或焦点变化可能导致MediaQuery更新,间接触发InputField所在的FadeInUp组件重建。

解决方案

方案1:为FadeInUp添加唯一Key

给每个FadeInUp组件分配GlobalKey(),确保Flutter识别为同一个Widget,避免重建时重复执行动画。

修改后的主组件代码:

children: <Widget>[
    Column(
      children: <Widget>[
        Text("Iniciar Sesión",
            style: TextStyle(
                fontSize: 30, fontWeight: FontWeight.bold)),
        SizedBox(height: 2),
        Text("Inicia sesión en tu cuenta",
            style:
                TextStyle(fontSize: 15, color: Colors.grey[700])),
      ],
    ),
    FadeInUp(
        key: GlobalKey(),
        delay: Duration(milliseconds: 100),
        duration: Duration(milliseconds: 1500),
        child: InputField(
            maxLenght: 35,
            labelText: "Email",
            controller: _emailController,
            icon: Iconsax.user,
            hintText: "Correo electronico")),
    FadeInUp(
        key: GlobalKey(),
        delay: Duration(milliseconds: 100),
        duration: Duration(milliseconds: 1500),
        child: InputField(
            isPassword: true,
            maxLenght: 15,
            controller: _passwordController,
            labelText: "Password",
            icon: Iconsax.key,
            hintText: "Contraseña")),
    FadeInUp(
        key: GlobalKey(),
        delay: Duration(milliseconds: 100),
        duration: Duration(milliseconds: 1500),
        child: SubmitButton(
            onPressed: _handleSubmit,
            buttonText: "Iniciar Sesión")),
]

方案2:优化InputField的宽度计算逻辑

将屏幕宽度计算移到父组件中,避免InputField因MediaQuery变化频繁重建,间接减少动画触发的可能。

修改InputField组件:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class InputField extends StatelessWidget {
  final String labelText;
  final String hintText;
  final int maxLenght;
  final IconData? icon;
  final double width; // 改为直接接收宽度值
  final bool isPassword;
  final TextEditingController? controller;
  final List<TextInputFormatter>? inputFormatters;
  final Function(String)? onChanged;

  const InputField({
    super.key,
    required this.labelText,
    this.hintText = "",
    this.maxLenght = 20,
    this.icon,
    required this.width, // 设为必填参数
    this.isPassword = false,
    this.controller,
    this.inputFormatters,
    this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
        width: width, // 使用传入的宽度值
        child: TextField(
          textCapitalization: TextCapitalization.sentences,
          autocorrect: true,
          enableSuggestions: true,
          obscureText: isPassword,
          maxLength: maxLenght,
          controller: controller,
          inputFormatters: inputFormatters,
          onChanged: onChanged,
          decoration: InputDecoration(
              labelText: labelText,
              hintText: hintText,
              alignLabelWithHint: true,
              labelStyle: TextStyle(
                  color: Colors.black,
                  fontSize: 14,
                  fontWeight: FontWeight.w400),
              prefixIcon: icon != null
                  ? Icon(
                      icon,
                      color: Colors.black,
                      size: 18,
                    )
                  : null,
              enabledBorder: OutlineInputBorder(
                  borderSide: BorderSide(
                      color: const Color.fromARGB(255, 224, 219, 219),
                      width: 2),
                  borderRadius: BorderRadius.circular(10)),
              floatingLabelStyle:
                  TextStyle(color: Colors.black, fontSize: 18),
              focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.black, width: 1.5),
                  borderRadius: BorderRadius.circular(10)))));
  }
}

父组件中使用时提前计算宽度:

// 在父组件的build方法内计算宽度
final inputWidth = MediaQuery.of(context).size.width * 0.9;

// 传入InputField
FadeInUp(
    key: GlobalKey(),
    delay: Duration(milliseconds: 100),
    duration: Duration(milliseconds: 1500),
    child: InputField(
        width: inputWidth,
        maxLenght: 35,
        labelText: "Email",
        controller: _emailController,
        icon: Iconsax.user,
        hintText: "Correo electronico")),

说明

方案1是最直接的修复方式,通过Key稳定Widget身份;方案2从根源减少不必要的重建,适合需要优化性能的场景。两者结合使用效果更佳。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:53:08