Flutter中FadeInUp动画在TextField交互时重复触发问题
animate_do FadeInUp动画在TextField切换焦点时重复触发的解决方案
问题
使用animate_do: ^3.1.2库为TextField添加FadeInUp动画时,切换输入框焦点会导致动画重复执行,但输入的文本会保留。相同代码在其他场景下无此问题。
原因
- Flutter中,若Widget无唯一标识(Key),重建时会被视为新Widget,animate_do的动画组件会重新初始化并执行动画。切换TextField焦点时,若父组件或InputField发生重建,未加Key的FadeInUp会重复触发动画。
- 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
相关产品推荐
相关产品推荐

