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

Flutter如何通过AnimationController实现文本逐字增长显示动画

解决方案

你的代码没生效的核心原因是updateText()从未在动画执行过程中被调用,动画从0到1推进时没有触发文本的重新计算和界面刷新,所以Text组件始终拿不到逐字变化的内容。
下面给你两种可直接落地的实现,第二种更简洁,推荐使用。

方案一:贴合你原有代码结构,补全动画监听逻辑

你只需要给动画控制器添加值变化监听,每次动画进度更新时重新计算显示文本、触发界面刷新,同时补全边界异常处理即可:

  1. 首先修改initState方法,给控制器加监听:
@override
void initState() {
  super.initState();
  developer.log('_AnimatedTextState init ');
  _appearanceController = AnimationController(vsync: this)
    // 每次动画值更新时重新计算文本、刷新界面
    ..addListener(() {
      updateText();
      setState(() {});
    })
    // 监听动画状态,隐藏动画跑完后清空文本
    ..addStatusListener((status) {
      if (status == AnimationStatus.dismissed) {
        setState(() {
          displayText = '';
        });
      }
    });
  displayText = '';
  if (widget.doShowMe) {
    _doShowMe();
  }
}
  1. 修复updateText的边界判断,避免空字符串越界:
void updateText() {
  final String payload = widget.textContent;
  if (payload.isEmpty) {
    displayText = '';
    return;
  }
  int numCharsToShow = (_appearanceController.value * payload.length).ceil();
  // 限制索引范围,防止越界报错
  numCharsToShow = numCharsToShow.clamp(0, payload.length);
  displayText = payload.substring(0, numCharsToShow);
}
  1. 补全didUpdateWidget里对文本内容变化的处理,新文本传入时重新触发打字动画:
@override
void didUpdateWidget(AnimatedText oldWidget) {
  super.didUpdateWidget(oldWidget);
  developer.log('_AnimatedTextState didUpdateWidget');
  // 处理显隐状态切换
  if (widget.doShowMe != oldWidget.doShowMe) {
    if (widget.doShowMe) {
      _doShowMe();
    } else {
      _hideIndicator();
    }
  }
  // 处理文本内容更新
  if (widget.textContent != oldWidget.textContent && widget.doShowMe) {
    _appearanceController.reset();
    _doShowMe();
  }
}

方案二:简化实现,去掉冗余状态

你已经用了AnimatedBuilder,它本身就会在动画每次更新时重新执行builder逻辑,完全没必要单独维护displayText变量、额外加监听,直接在builder里实时计算当前要显示的文本即可,代码更少也不容易出状态同步问题:

  • 删掉类里的displayText成员变量、删掉单独的updateText方法
  • 直接重写build方法:
@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
      animation: _appearanceController,
      builder: (context, child) {
        // 隐藏状态下直接返回空组件占位
        if (_appearanceController.status == AnimationStatus.dismissed) {
          return const SizedBox.shrink();
        }
        final payload = widget.textContent;
        // 计算当前帧要显示的字符数
        final charCount = payload.isEmpty
            ? 0
            : (_appearanceController.value * payload.length).ceil().clamp(0, payload.length);
        return Opacity(
          // 隐藏时加透明度渐变,避免生硬消失
          opacity: _appearanceController.status == AnimationStatus.reverse
              ? _appearanceController.value
              : 1,
          child: Text(payload.substring(0, charCount)),
        );
      });
}

原有的_doShowMe、_hideIndicator逻辑可以直接复用,不需要修改。

调优建议

  • 打字速度直接修改_doShowMe里的duration即可,比如设置为1500毫秒就是更慢的打字效果
  • 如果需要模拟打字光标效果,可以在显示的文本末尾拼一个charCount < payload.length ? '|' : '',再给光标单独加个周期闪烁的动画
  • 如果需要支持富文本逐字显示,把Text换成RichText,按当前字符数拆分TextSpan就行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 05:01:00