如何基于TextField的controller值实现Text组件的动画效果?
实现TextField字符增删的差异化动画
之前的方案仅在添加字符时生效,问题出在删除字符时对应的组件会被Flutter直接销毁,根本没机会播放动画。要解决这个问题,得跟踪每个字符的生命周期,让删除操作先触发动画,等动画结束再移除字符。
具体实现步骤
1. 定义字符状态类
创建类存储每个字符的核心信息,包括字符内容、唯一ID(区分重复字符)和当前状态:
class CharItem { final String char; final String id; CharStatus status; CharItem({ required this.char, required this.id, this.status = CharStatus.adding, }); } enum CharStatus { adding, normal, removing }
2. 监听文本变化,更新字符状态
对比新旧文本内容,筛选出新增和待删除的字符,分别标记对应状态:
void _handleTextChange(String newText) { final oldChars = [..._charItems]; final newChars = newText.split('').map((c) => CharItem( char: c, id: '${DateTime.now().microsecondsSinceEpoch}_$c', // 生成唯一ID )).toList(); // 筛选新增字符 final addedChars = newChars.where((newChar) => !oldChars.any((old) => old.char == newChar.char && old.status != CharStatus.removing) ).toList(); // 筛选待删除字符 final removedChars = oldChars.where((old) => !newChars.any((newChar) => newChar.char == old.char) ).toList(); // 添加新字符并标记为添加状态 setState(() => _charItems.addAll(addedChars)); // 标记待删除字符状态,动画结束后移除 for (var item in removedChars) { final index = _charItems.indexOf(item); if (index != -1) { setState(() => _charItems[index].status = CharStatus.removing); Future.delayed(const Duration(milliseconds: 300), () { if (mounted) setState(() => _charItems.removeAt(index)); }); } } // 新增字符动画结束后改为正常状态 for (var item in addedChars) { Future.delayed(const Duration(milliseconds: 300), () { if (mounted) setState(() => item.status = CharStatus.normal); }); } }
3. 为每个字符绑定差异化动画
使用AnimatedBuilder根据字符状态渲染对应动画:添加时缩放淡入,删除时缩放淡出:
Row( mainAxisSize: MainAxisSize.min, children: _charItems.map((item) { return AnimatedBuilder( animation: _animController, builder: (context, child) { double scale = 1.0; double opacity = 1.0; if (item.status == CharStatus.adding) { scale = Tween(begin: 0.5, end: 1.0).animate( CurvedAnimation(parent: _animController, curve: Curves.easeOut) ).value; opacity = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animController, curve: Curves.easeOut) ).value; } else if (item.status == CharStatus.removing) { scale = Tween(begin: 1.0, end: 0.5).animate( CurvedAnimation(parent: _animController, curve: Curves.easeIn) ).value; opacity = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _animController, curve: Curves.easeIn) ).value; } return Transform.scale( scale: scale, child: Opacity( opacity: opacity, child: Text(item.char, style: const TextStyle(fontSize: 24)), ), ); }, ); }).toList(), )
4. 初始化控制器与监听
在State类中完成控制器初始化,并绑定文本变化监听:
late TextEditingController _textController; late AnimationController _animController; List<CharItem> _charItems = []; @override void initState() { super.initState(); _textController = TextEditingController(); _animController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _textController.addListener(() { _handleTextChange(_textController.text); _animController.forward(from: 0.0); }); } @override void dispose() { _textController.dispose(); _animController.dispose(); super.dispose(); }
关键注意事项
- 必须为每个字符生成唯一ID,否则连续输入相同字符时会出现判断错误。
- 删除字符时不能直接从列表移除,需先标记删除状态播放动画,动画结束后再执行移除操作。
- 使用
mounted判断组件是否存活,避免异步更新状态时出现崩溃。
内容的提问来源于stack exchange,提问作者Mohamed Amara
相关产品推荐
相关产品推荐

