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

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),
        ),
      ),
    );
  }
}

原因分析

  1. TextField无控制器管理状态:blankWord里的TextField没有绑定TextEditingController,输入内容的状态由Flutter框架隐式管理。切换题目时,框架可能复用旧的输入状态,导致内容残留。
  2. StatefulWidget状态复用:TextWithBlanks和blankWord都是StatefulWidget,切换题目时如果没有设置唯一Key,Flutter会认为这是同一个Widget,复用旧的State实例,旧的输入内容不会被清空。
  3. 直接修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 10:57:21