Flutter中ListView滚动时选中的单选按钮自动取消选中问题
解决Flutter ListView滚动时单选按钮选中状态丢失问题
问题根源
- 子组件状态独立维护,未与父组件同步:MCQQuestionWidget内部的
_selectedOption仅在初始化时设为0,ListView滚动导致组件销毁重建时,状态会重置,选中状态丢失。 - 父组件的选中状态未同步到子组件:虽然父组件传递了
selectedOption参数,但子组件没有利用该参数初始化或更新自身状态。 - 总分计算逻辑有缺陷:原累加式的
updateTotalMarks会在组件重建时重复计算,导致总分错误。
解决方案
步骤1:修改MCQQuestionWidget,同步父组件状态
让子组件依赖父组件传递的选中状态,而非独立维护状态,并在父状态变化时同步更新自身。
class MCQQuestionWidget extends StatefulWidget { final MCQQuestionModel question; final int questionNumber; final double negetivemark; final String selectedOption; final Function(String) onOptionSelected; MCQQuestionWidget({ required super.key, required this.question, required this.questionNumber, required this.negetivemark, required this.selectedOption, required this.onOptionSelected, }); @override _MCQQuestionWidgetState createState() => _MCQQuestionWidgetState(); } class _MCQQuestionWidgetState extends State<MCQQuestionWidget> { late int _selectedOption; bool _showAnswer = false; bool _isAnswerCorrect = false; bool _questionLocked = false; @override void initState() { super.initState(); // 从父组件参数初始化选中状态 _selectedOption = widget.selectedOption.isEmpty ? 0 : getOptionNumber(widget.selectedOption); if (_selectedOption != 0) { _isAnswerCorrect = _selectedOption == getOptionNumber(widget.question.answer); _questionLocked = true; } } @override void didUpdateWidget(covariant MCQQuestionWidget oldWidget) { super.didUpdateWidget(oldWidget); // 父组件选中状态变化时,同步更新子组件 if (oldWidget.selectedOption != widget.selectedOption) { setState(() { _selectedOption = widget.selectedOption.isEmpty ? 0 : getOptionNumber(widget.selectedOption); _isAnswerCorrect = _selectedOption == getOptionNumber(widget.question.answer); _questionLocked = _selectedOption != 0; }); } } int getOptionNumber(String option) { switch (option) { case 'option1': return 1; case 'option2': return 2; case 'option3': return 3; case 'option4': return 4; default: return 0; } } String getOptionKey(int optionNumber) { switch (optionNumber) { case 1: return 'option1'; case 2: return 'option2'; case 3: return 'option3'; case 4: return 'option4'; default: return ''; } } void _checkAnswer(int selectedOption) { if (!_questionLocked) { String optionKey = getOptionKey(selectedOption); setState(() { _selectedOption = selectedOption; _isAnswerCorrect = selectedOption == getOptionNumber(widget.question.answer); _questionLocked = true; }); // 通知父组件更新全局选中状态 widget.onOptionSelected(optionKey); } } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ... 问题标题部分保持不变 CustomRadioButton( label: "ক) ${widget.question.option1}", isSelected: _selectedOption == 1, isEnabled: !_questionLocked, onSelect: (isSelected) { setState(() { _selectedOption = isSelected ? 1 : 0; _checkAnswer(1); }); }, isCorrectAnswer: widget.question.answer == 'option1', ), CustomRadioButton( label: "খ) ${widget.question.option2}", isSelected: _selectedOption == 2, isEnabled: !_questionLocked, onSelect: (isSelected) { setState(() { _selectedOption = isSelected ? 2 : 0; _checkAnswer(2); }); }, isCorrectAnswer: widget.question.answer == 'option2', ), CustomRadioButton( label: "গ) ${widget.question.option3}", isSelected: _selectedOption == 3, isEnabled: !_questionLocked, onSelect: (isSelected) { setState(() { _selectedOption = isSelected ? 3 : 0; _checkAnswer(3); }); }, isCorrectAnswer: widget.question.answer == 'option3', ), CustomRadioButton( label: "ঘ) ${widget.question.option4}", isSelected: _selectedOption == 4, isEnabled: !_questionLocked, onSelect: (isSelected) { setState(() { _selectedOption = isSelected ? 4 : 0; _checkAnswer(4); }); }, isCorrectAnswer: widget.question.answer == 'option4', ), // ... 答案展示按钮部分保持不变 ], ); } }
步骤2:修正QuestionDetailsPage的总分计算逻辑
改为基于全局选中状态实时计算总分,避免重复累加问题。
class _QuestionDetailsPageState extends State<QuestionDetailsPage> { late Future<List<MCQQuestionModel>> _questionsFuture; DatabaseHelper _databaseHelper = DatabaseHelper(); Map<int, String> selectedOptions = {}; List<MCQQuestionModel>? _questions; // 实时计算总分 double get _totalMarks { double total = 0; for (var entry in selectedOptions.entries) { int qid = entry.key; String selected = entry.value; var question = _questions?.firstWhere((q) => q.qid == qid); if (question != null) { total += selected == question.answer ? question.questionmark : -widget.negetivemark; } } return total; } @override void initState() { super.initState(); _questionsFuture = _databaseHelper.getQuestionsForExam(eid: widget.eid).then((questions) { setState(() { _questions = questions; }); return questions; }); } final gradient = LinearGradient( colors: [Colors.blue, Colors.green], begin: Alignment.topLeft, end: Alignment.bottomRight, ); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar(), floatingActionButton: FloatingActionButton( onPressed: () {}, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(25.0), gradient: gradient, ), child: Padding( padding: const EdgeInsets.all(8.0), child: Text( ' $_totalMarks', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), ), ), ), body: Center( child: Stack( alignment: Alignment.center, children: [ Opacity( opacity: 0.1, child: Image.asset( 'assests/logo.png', fit: BoxFit.cover, ), ), FutureBuilder<List<MCQQuestionModel>>( future: _questionsFuture, builder: (context, snapshot) { if (snapshot.hasData) { List<MCQQuestionModel> questions = snapshot.data!; return ListView.builder( itemCount: questions.length, itemBuilder: (context, index) { final question = questions[index]; return MCQQuestionWidget( key: PageStorageKey<int>(question.qid), question: question, questionNumber: index + 1, selectedOption: selectedOptions[question.qid] ?? '', onOptionSelected: (selectedOption) { setState(() { selectedOptions[question.qid] = selectedOption; }); }, negetivemark: widget.negetivemark, ); }, ); } else { return Center(child: CircularProgressIndicator()); } }, ), ], ), ), ); } }
核心逻辑说明
- 将选中状态的唯一数据源放在父组件的
selectedOptions映射中,子组件仅负责展示和交互,避免状态分散。 - 子组件通过
didUpdateWidget监听父组件参数变化,确保组件重建或父状态更新时,子组件状态同步。 - 总分采用实时计算方式,基于当前所有选中选项的对错情况动态计算,保证数据一致性。
内容的提问来源于stack exchange,提问作者fluttercoder
相关产品推荐
相关产品推荐

