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

Flutter:每个ListTile独立双单选按钮(出勤/缺勤)问题解决

问题原因

你当前所有单选按钮都绑定了同一个selectedRadio全局变量,导致任何一个Radio被选中时,所有条目的Radio都会同步更新状态,自然无法实现每个学生独立的出勤/缺勤标记。

解决方案

给每个学生单独维护出勤状态,具体实现步骤如下:

  1. 创建学生数据模型
    定义Student类,包含姓名和出勤状态字段,让每个学生拥有独立的状态:
class Student {
  final String name;
  int attendanceStatus; // 0=出勤(绿色), 1=缺勤(红色)

  Student(this.name, {this.attendanceStatus = 0});
}
  1. 修改列表类型
    把原来的List<String> _students替换为List<Student> _students,用对象存储每个学生的姓名和状态。

  2. 调整添加学生逻辑
    添加新学生时,创建Student实例并默认设置出勤状态为0:

if (result != null) {
  setState(() {
    _students.add(Student(result));
  });
}
  1. 更新单选按钮绑定逻辑
    在ListView的itemBuilder中,让每个Radio绑定对应Student的attendanceStatus,点击时仅修改当前学生的状态:
Radio(
  activeColor: Colors.green,
  value: 0,
  groupValue: _students[index].attendanceStatus,
  onChanged: (val) {
    setState(() {
      _students[index].attendanceStatus = val!;
    });
  },
),
Radio(
  activeColor: Colors.red,
  value: 1,
  groupValue: _students[index].attendanceStatus,
  onChanged: (val) {
    setState(() {
      _students[index].attendanceStatus = val!;
    });
  },
)
  1. 适配其他关联逻辑
    比如ListTile标题改为显示学生姓名Text(_students[index].name),删除逻辑直接移除对应Student对象即可。
修改后的完整代码
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';

class Student {
  final String name;
  int attendanceStatus; // 0=出勤(绿色), 1=缺勤(红色)

  Student(this.name, {this.attendanceStatus = 0});
}

class InsideList extends StatefulWidget {
  final String name;

  InsideList(this.name);

  @override
  State<InsideList> createState() => _InsideListState();
}

class _InsideListState extends State<InsideList> {
  List<Student> _students = [];

  late TextEditingController _textController;

  @override
  void initState() {
    super.initState();
    _textController = TextEditingController();
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.name),
        centerTitle: true,
        backgroundColor: const Color.fromARGB(255, 22, 37, 50),
        toolbarHeight: 65,
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(
            bottom: Radius.circular(30),
          ),
        ),
      ),
      body: _students.length > 0
          ? ListView.separated(
              itemCount: _students.length,
              itemBuilder: (_, index) {
                return ListTile(
                  leading: const Icon(Icons.person),
                  trailing: FittedBox(
                    fit: BoxFit.fill,
                    child: Row(
                      children: [
                        Radio(
                            activeColor: Colors.green,
                            value: 0,
                            groupValue: _students[index].attendanceStatus,
                            onChanged: (val) {
                              setState(() {
                                _students[index].attendanceStatus = val!;
                              });
                            }),
                        Radio(
                          activeColor: Colors.red,
                          value: 1,
                          groupValue: _students[index].attendanceStatus,
                          onChanged: (val) {
                            setState(() {
                              _students[index].attendanceStatus = val!;
                            });
                          },
                        )
                      ],
                    ),
                  ),
                  title: Center(child: Text(_students[index].name)),
                  onTap: () {
                    Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: ((context) =>
                                InsideList(_students[index].name))));
                  },
                  onLongPress: (() async {
                    await showDialog(
                        context: context,
                        builder: ((context) {
                          return AlertDialog(
                            title: const Text(
                              "Are you sure you want to delete this student?",
                              style: TextStyle(fontSize: 15),
                            ),
                            actions: [
                              TextButton(
                                  child: Text("cancel"),
                                  onPressed: (() {
                                    Navigator.pop(context);
                                  })),
                              TextButton(
                                child: Text('Delete'),
                                onPressed: () {
                                  setState(() {
                                    _students.removeAt(index);
                                    Navigator.pop(context);
                                  });
                                },
                              ),
                            ],
                          );
                        }));
                  }),
                );
              },
              separatorBuilder: (BuildContext context, int index) =>
                  const Divider(
                color: Colors.black,
              ),
            )
          : const Center(
              child: Text("You currently have no students. Add from below."),
            ),
      floatingActionButton: SpeedDial(
        animatedIcon: AnimatedIcons.menu_arrow,
        spacing: 6,
        spaceBetweenChildren: 6,
        backgroundColor: const Color.fromARGB(255, 22, 37, 50),
        foregroundColor: const Color.fromARGB(255, 255, 255, 255),
        children: [
          SpeedDialChild(
            child: const Icon(Icons.group_add),
            label: "add student",
            onTap: () async {
              final result = await showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: const Text('Add a new student'),
                    content: TextField(
                      controller: _textController,
                      autofocus: true,
                      decoration: const InputDecoration(
                          hintText: "Enter the name of the student."),
                    ),
                    actions: [
                      TextButton(
                        child: Text('Cancel'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                      TextButton(
                        child: Text('Add'),
                        onPressed: () {
                          Navigator.pop(context, _textController.text);
                          _textController.clear();
                        },
                      ),
                    ],
                  );
                },
              );
              if (result != null) {
                setState(() {
                  _students.add(Student(result));
                });
              }
            },
          ),
        ],
      ),
    );
  }
}
关键说明
  • 移除了全局的selectedRadio变量,改为每个Student实例维护自己的attendanceStatus
  • 每个条目里的Radio组件仅绑定对应学生的状态,点击时仅更新当前学生的状态
  • 数据模型的设计让状态管理更清晰,后续扩展更多学生属性也更方便

内容的提问来源于stack exchange,提问作者Killertouch

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:55:37