Flutter填空题APP:页面切换后TextField输入内容丢失的解决方法
问题分析
输入内容丢失的核心原因有两点:
- PageView 默认会销毁当前视窗外的页面,切换回来时会重建
TextWithBlanks和BlankWord,之前的输入值因存储在子组件内部而被重置。 - 当前输入值直接存在
BlankWord的 widget 属性中,违反 Flutter 状态管理原则,且无法在组件重建后保留数据。
解决方案
通过状态提升将所有输入数据统一管理在父组件,并结合页面状态保持、文本控制器实现输入值持久化,具体步骤如下:
1. 父组件统一管理填空数据
在 _PlayGameState 中创建数据结构,存储每个题目下所有填空的答案、用户输入和正确状态:
class PlayGame extends StatefulWidget { final List document; PlayGame({Key? key, required this.document}) : super(key: key); @override State<PlayGame> createState() => _PlayGameState(); } class _PlayGameState extends State<PlayGame> { int quizCount = 0; // 存储每个题的填空数据:[题目索引][填空索引] -> {答案、用户输入、是否正确} List<List<Map<String, dynamic>>> quizAnswers = []; @override void initState() { super.initState(); // 初始化数据结构,解析每个题目中的填空 for (var text in widget.document) { final split = TextWithBlanks.regex.split(text); List<Map<String, dynamic>> blanks = []; for (var part in split) { if (part.startsWith('{')) { String answer = part.substring(1, part.length - 1); blanks.add({ 'answer': answer, 'userInput': '', 'isCorrect': false, }); } } quizAnswers.add(blanks); } } // 更新指定题、指定填空的输入值 void updateUserInput(int quizIndex, int blankIndex, String input) { setState(() { quizAnswers[quizIndex][blankIndex]['userInput'] = input; quizAnswers[quizIndex][blankIndex]['isCorrect'] = input == quizAnswers[quizIndex][blankIndex]['answer']; }); } // 获取全部正确填空数量 int getCorrectCount() { int count = 0; for (var quiz in quizAnswers) { for (var blank in quiz) { if (blank['isCorrect'] == true) count++; } } return count; } @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: [ if (quizCount == 5) GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) => Result())); print(getCorrectCount()); }, child: const Row( children: [ Text("採点する", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), Icon(Icons.arrow_forward, size: 20), SizedBox(width: 10) ], ), ) else const SizedBox.shrink() ], automaticallyImplyLeading: false, ), body: PageView( onPageChanged: (counter) => setState(() => quizCount = counter + 1), children: [ QuizText( quizIndex: 0, text: widget.document[0], blanks: quizAnswers[0], onInputChanged: (i, input) => updateUserInput(0, i, input), ), QuizText( quizIndex: 1, text: widget.document[1], blanks: quizAnswers[1], onInputChanged: (i, input) => updateUserInput(1, i, input), ), QuizText( quizIndex: 2, text: widget.document[2], blanks: quizAnswers[2], onInputChanged: (i, input) => updateUserInput(2, i, input), ), QuizText( quizIndex: 3, text: widget.document[3], blanks: quizAnswers[3], onInputChanged: (i, input) => updateUserInput(3, i, input), ), QuizText( quizIndex: 4, text: widget.document[4], blanks: quizAnswers[4], onInputChanged: (i, input) => updateUserInput(4, i, input), ), ], ), ); } }
2. 保持PageView页面状态
给 QuizText 添加 AutomaticKeepAliveClientMixin,避免PageView销毁不可见页面:
class QuizText extends StatefulWidget { final int quizIndex; final String text; final List<Map<String, dynamic>> blanks; final Function(int, String) onInputChanged; const QuizText({ Key? key, required this.quizIndex, required this.text, required this.blanks, required this.onInputChanged, }) : super(key: key); @override State<QuizText> createState() => _QuizTextState(); } class _QuizTextState extends State<QuizText> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { super.build(context); // 必须调用super.build return Container( constraints: const BoxConstraints.expand(), child: Padding( padding: const EdgeInsets.all(5.0), child: Card( child: SizedBox( height: double.infinity, child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(bottom: 30.0), child: TextWithBlanks( text: widget.text, blanks: widget.blanks, onInputChanged: widget.onInputChanged, ), ), ), ), ), ), ); } }
3. 修改TextWithBlanks传递数据
让 TextWithBlanks 接收父组件的填空数据,并传递给每个 BlankWord:
class TextWithBlanks extends StatefulWidget { final String text; static final regex = RegExp("(?={)|(?<=})"); final List<Map<String, dynamic>> blanks; final Function(int, String) onInputChanged; TextWithBlanks({ Key? key, required this.text, required this.blanks, required this.onInputChanged, }) : 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); int blankIndex = 0; 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( answer: widget.blanks[blankIndex]['answer'], userInput: widget.blanks[blankIndex]['userInput'], onChanged: (input) => widget.onInputChanged(blankIndex++, input), ), ) : TextSpan(text: text), ], ), ), ); } }
4. 用TextEditingController控制输入框
修改 BlankWord(类名首字母大写符合Dart规范),使用控制器同步输入值:
class BlankWord extends StatefulWidget { final String answer; final String userInput; final Function(String) onChanged; const BlankWord({ Key? key, required this.answer, required this.userInput, required this.onChanged, }) : super(key: key); @override State<BlankWord> createState() => _BlankWordState(); } class _BlankWordState extends State<BlankWord> { late TextEditingController _controller; @override void initState() { super.initState(); _controller = TextEditingController(text: widget.userInput); } @override void didUpdateWidget(covariant BlankWord oldWidget) { super.didUpdateWidget(oldWidget); // 父组件数据更新时同步控制器文本 if (oldWidget.userInput != widget.userInput) { _controller.text = widget.userInput; } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { double answerWidth = widget.answer.length * 15.0; return SizedBox( width: answerWidth, child: TextFormField( controller: _controller, maxLines: null, cursorColor: Colors.grey, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), autofocus: false, maxLength: widget.answer.length + 5, onChanged: widget.onChanged, decoration: const InputDecoration( counterText: "", hintStyle: TextStyle(color: Colors.grey, fontSize: 12), ).copyWith(hintText: widget.answer), ), ); } }
关键修改说明
- 状态提升:所有填空数据集中在父组件管理,确保页面切换时数据不丢失。
- 页面保活:通过
AutomaticKeepAliveClientMixin避免PageView销毁页面,减少不必要的重建。 - 控制器同步:用
TextEditingController绑定输入框,确保输入值与父组件数据实时同步。 - 单向数据流:子组件通过回调通知父组件更新数据,符合Flutter状态管理最佳实践。
内容的提问来源于stack exchange,提问作者wasa-bi
相关产品推荐
相关产品推荐

