Flutter逐字打字动画及光标跟随:多行文本适配问题
Flutter 多行文本打字动画修复方案
需求:实现类人类的打字效果,逐字符显示文本,光标跟随字符显示进度,支持多行文本。
现有问题:代码仅在文本第一行时光标跟随正常,多行时光标位置错误。
问题原因分析
原代码使用Wrap组件分别包裹显示文本和光标,Wrap会将光标视为独立的布局元素。当文本因宽度限制换行时,光标无法正确附着在当前输入的字符末尾,导致位置偏移,无法跟随多行文本的输入进度。
修改后的完整代码
import 'package:flutter/material.dart'; import 'dart:async'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Typing Animation', theme: ThemeData( primarySwatch: Colors.blue, ), home: TypingScreen(), ); } } class TypingScreen extends StatefulWidget { @override _TypingScreenState createState() => _TypingScreenState(); } class _TypingScreenState extends State<TypingScreen> { final String sentence = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."; int currentIndex = 0; bool showCursor = true; final ScrollController _scrollController = ScrollController(); late Timer _cursorTimer; @override void initState() { super.initState(); startTypingAnimation(); // 独立控制光标闪烁,避免与字符更新冲突 _cursorTimer = Timer.periodic(const Duration(milliseconds: 500), (_) { if (currentIndex <= sentence.length) { setState(() => showCursor = !showCursor); } }); } void startTypingAnimation() { Future.delayed(const Duration(milliseconds: 100), () { if (currentIndex < sentence.length) { setState(() => currentIndex++); // 文本更新后滚动到最新位置 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 100), curve: Curves.easeOut, ); }); startTypingAnimation(); } else { // 打字结束后保持光标显示 setState(() => showCursor = true); _cursorTimer.cancel(); } }); } @override void dispose() { _scrollController.dispose(); _cursorTimer.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final displayedText = sentence.substring(0, currentIndex); return Scaffold( appBar: AppBar( title: const Text('Typing Animation'), ), body: Center( child: SingleChildScrollView( controller: _scrollController, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 300, padding: const EdgeInsets.symmetric(horizontal: 8), child: RichText( text: TextSpan( style: const TextStyle(fontSize: 24, color: Colors.black), children: [ TextSpan(text: displayedText), // 将光标作为文本的一部分,跟随文本末尾 if (showCursor) const TextSpan( text: '|', style: TextStyle(fontSize: 24), ), ], ), ), ), ], ), ), ), ); } }
关键修改说明
- 整合光标到文本流:使用
RichText将光标作为TextSpan的一部分,让光标成为文本内容的末尾元素,彻底解决换行时光标错位问题。 - 分离光标闪烁逻辑:用独立的
Timer控制光标闪烁,避免与字符更新的状态更新相互干扰,保证闪烁动画稳定。 - 实时滚动适配:每次字符更新后,通过
WidgetsBinding.instance.addPostFrameCallback触发滚动,确保最新输入的文本始终处于可视区域。 - 完善资源管理:在组件销毁时清理
Timer和ScrollController,避免内存泄漏。
内容的提问来源于stack exchange,提问作者seazon96
相关产品推荐
相关产品推荐

