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

Flutter中ListView滚动时选中的单选按钮自动取消选中问题

解决Flutter ListView滚动时单选按钮选中状态丢失问题

问题根源

  1. 子组件状态独立维护,未与父组件同步:MCQQuestionWidget内部的_selectedOption仅在初始化时设为0,ListView滚动导致组件销毁重建时,状态会重置,选中状态丢失。
  2. 父组件的选中状态未同步到子组件:虽然父组件传递了selectedOption参数,但子组件没有利用该参数初始化或更新自身状态。
  3. 总分计算逻辑有缺陷:原累加式的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 11:54:50