Flutter填空题应用切换题目时TextField内容残留的原因排查
填空题测验应用切换题目时TextField内容残留问题
我正在开发一款填空题测验应用,每套测验包含5道题目。但切换至下一道题目时,TextField中输入的内容会残留,请帮忙分析可能的原因。
相关代码
PlayGame组件
class PlayGame extends StatefulWidget { final List document; List correctList = []; PlayGame({Key? key, required this.document}) : super(key: key); @override State<PlayGame> createState() => _PlayGameState(); } class _PlayGameState extends State<PlayGame> { int quizNum = 0; int quizCount = 1; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: Center( child: Text( "$quizCount/5", style: const TextStyle( fontSize: 25, fontStyle: FontStyle.italic, fontWeight: FontWeight.bold), ), ), actions: [ Row( children: [ IconButton( onPressed: () { setState( () { if (quizNum < 4) { quizNum += 1; quizCount += 1; } else if (quizNum == 4) { print(widget.correctList.length); Navigator.push( context, MaterialPageRoute(builder: (context) => Result()), ); } }, ); }, icon: const Icon( Icons.arrow_circle_right_outlined, size: 40, ), ), const SizedBox( width: 10, ) ], ) ], automaticallyImplyLeading: false, ), body: SizedBox( height: double.infinity, child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(bottom: 30.0), child: TextWithBlanks( text: widget.document[quizNum], correctList: widget.correctList), ), ), ), ); } }
TextWithBlanks组件
class TextWithBlanks extends StatefulWidget { final String text; static final regex = RegExp("(?={)|(?<=})"); List correctList = []; TextWithBlanks({Key? key, required this.text, required this.correctList}) : super(key: key); @override State<TextWithBlanks> createState() => _TextWithBlanksState(); } class _TextWithBlanksState extends State<TextWithBlanks> { @override Widget build(BuildContext context) { final split = widget.text.split(TextWithBlanks.regex); return Padding( padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0), child: Text.rich( TextSpan( style: const TextStyle(fontSize: 15, height: 3.0), children: <InlineSpan>[ for (String text in split) text.startsWith('{') ? WidgetSpan( child: blankWord(text.substring(1, text.length - 1), widget.correctList), ) : TextSpan(text: text), ], ), ), ); } }
BlankWord组件
class _blankWordState extends State<blankWord> { @override Widget build(BuildContext context) { return SizedBox( width: widget.answerWidth, child: TextField( maxLines: null, cursorColor: Colors.grey, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), autofocus: false, maxLength: widget.answerLength + 5, onChanged: (enterWord) { widget.value = enterWord; if (enterWord == widget.answer) { if (widget.answerBool == false) { widget.answerBool = true; widget.correctList.add(widget.answer); } } else { if (widget.answerBool == true) { widget.answerBool = false; widget.correctList.remove(widget.answer); } } }, decoration: InputDecoration( counterText: "", hintText: widget.answerHint, hintStyle: const TextStyle(color: Colors.grey, fontSize: 12), ), ), ); } }
原因分析
- TextField无控制器管理状态:
blankWord里的TextField没有绑定TextEditingController,输入内容的状态由Flutter框架隐式管理。切换题目时,框架可能复用旧的输入状态,导致内容残留。 - StatefulWidget状态复用:
TextWithBlanks和blankWord都是StatefulWidget,切换题目时如果没有设置唯一Key,Flutter会认为这是同一个Widget,复用旧的State实例,旧的输入内容不会被清空。 - 直接修改Widget成员变量:在
onChanged中直接修改widget.value、widget.answerBool等StatefulWidget的成员变量,不符合Flutter状态管理规范,容易导致状态混乱,不同题目的输入状态相互干扰。
修复方案
1. 给TextField添加TextEditingController
在blankWord的State中创建控制器,统一管理输入内容,并且在题目切换时清空输入框。
修改后的blankWord代码:
class blankWord extends StatefulWidget { final String answer; final List correctList; final double answerWidth; final int answerLength; final String answerHint; String value = ""; bool answerBool = false; blankWord({ Key? key, required this.answer, required this.correctList, required this.answerWidth, required this.answerLength, required this.answerHint, }) : super(key: key); @override State<blankWord> createState() => _blankWordState(); } class _blankWordState extends State<blankWord> { late TextEditingController _controller; @override void initState() { super.initState(); _controller = TextEditingController(); } @override void dispose() { _controller.dispose(); super.dispose(); } // 当Widget更新(切换题目)时,重置输入状态 @override void didUpdateWidget(covariant blankWord oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.answer != widget.answer) { _controller.clear(); widget.value = ""; widget.answerBool = false; // 移除旧答案的正确标记 if (widget.correctList.contains(widget.answer)) { widget.correctList.remove(widget.answer); } } } @override Widget build(BuildContext context) { return SizedBox( width: widget.answerWidth, child: TextField( controller: _controller, maxLines: null, cursorColor: Colors.grey, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), autofocus: false, maxLength: widget.answerLength + 5, onChanged: (enterWord) { widget.value = enterWord; if (enterWord == widget.answer) { if (!widget.answerBool) { widget.answerBool = true; widget.correctList.add(widget.answer); } } else { if (widget.answerBool) { widget.answerBool = false; widget.correctList.remove(widget.answer); } } }, decoration: InputDecoration( counterText: "", hintText: widget.answerHint, hintStyle: const TextStyle(color: Colors.grey, fontSize: 12), ), ), ); } }
2. 给StatefulWidget设置唯一Key
确保切换题目时,Flutter销毁旧的State并创建新实例,避免状态复用。
修改PlayGame中创建TextWithBlanks的代码:
TextWithBlanks( key: ValueKey(quizNum), // 用当前题目编号作为唯一标识 text: widget.document[quizNum], correctList: widget.correctList )
修改TextWithBlanks中创建blankWord的代码:
WidgetSpan( child: blankWord( key: ValueKey(text.substring(1, text.length - 1)), // 用答案内容作为唯一标识 answer: text.substring(1, text.length - 1), correctList: widget.correctList, answerWidth: /* 你的宽度值 */, answerLength: /* 你的长度值 */, answerHint: /* 你的提示文本 */, ), )
3. 优化状态管理(可选)
避免直接修改widget的成员变量,把状态移到State内部管理,让代码更符合Flutter规范:
class _blankWordState extends State<blankWord> { late TextEditingController _controller; bool _isCorrect = false; String _inputValue = ""; @override void initState() { super.initState(); _controller = TextEditingController(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override void didUpdateWidget(covariant blankWord oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.answer != widget.answer) { _controller.clear(); _isCorrect = false; _inputValue = ""; if (widget.correctList.contains(widget.answer)) { widget.correctList.remove(widget.answer); } } } @override Widget build(BuildContext context) { return SizedBox( width: widget.answerWidth, child: TextField( controller: _controller, maxLines: null, cursorColor: Colors.grey, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), autofocus: false, maxLength: widget.answerLength + 5, onChanged: (enterWord) { setState(() { _inputValue = enterWord; if (enterWord == widget.answer) { if (!_isCorrect) { _isCorrect = true; widget.correctList.add(widget.answer); } } else { if (_isCorrect) { _isCorrect = false; widget.correctList.remove(widget.answer); } } }); }, decoration: InputDecoration( counterText: "", hintText: widget.answerHint, hintStyle: const TextStyle(color: Colors.grey, fontSize: 12), ), ), ); } }
内容的提问来源于stack exchange,提问作者wasa-bi
相关产品推荐
相关产品推荐

