【问题标题】:How to check/uncheck a Checkbox inside a stateful child WIdget in Flutter?如何在 Flutter 中选中/取消选中有状态子 WIdget 中的复选框?
【发布时间】:2021-03-20 10:40:41
【问题描述】:

我有一个名为LockTimelogsList 的父窗口小部件,其中包含LockTimelogBoxListView。每个子小部件/LockTimelogBox 包含一个 Checkbox 我需要从父 LockTimelogsList 更新。当用户按下“全选”或“取消全选”时,LockTimelogsList 需要遍历 LockTimelogBoxes 并将其中的复选框设置为 true/false。

我遇到的麻烦是迭代列出的子小部件并更改和更新它们的复选框布尔值,即使在调用 setState 之后,复选框也不会更新它的视图。

我尝试了两种方法:

  • 在 setState 调用中直接从父级更改复选框 bool 值。
  • 在每个子窗口小部件内调用一个函数以从子窗口小部件内部更改复选框值。

更新

现在代码可以工作了。它会正确更新所有复选框。看看接受的答案,以防阅读这篇文章的人犯了与我相同的错误。我将代码更新为现在可以工作了。您可以在下面看到工作代码。

[![在此处输入图片描述][2]][2]

当前父(列表)代码:

class LockTimelogsList extends StatefulWidget {
  LockTimelogsList({Key key}) : super(key: key);

  @override
  _LockTimelogsListState createState() => _LockTimelogsListState();
}

class _LockTimelogsListState extends State<LockTimelogsList> {
  List<TimeLogModel> unlockedLogs;
  List<ProjectModel> projects;
  List<WorkOrderModel> workOrders;
  List<TaskModel> tasks;
  ProjectHttpService projectHttpService;
  WorkOrderHttpService workOrderHttpService;
  TaskHttpService taskHttpService;
  TimeLogHttpService timeLogHttpService;
  double displayHeight;
  double displayWidth;
  bool selectAllOptionActive;
  List<LockTimelogBox> timelogBoxes;
  List<Function> selectFunctions;
  List<Function> deselectFunctions;
  List<TimelogBoxData> boxDatas;
  List<bool> checkboxes;

  @override
  void initState() {
    initServices();
    initValues();
    loadInitData();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    setDisplayDimensions();
    return SafeArea(
        child: Scaffold(
      backgroundColor: Colors.white,
      appBar: buildAppBar(),
      body: buildBody(),
    ));
  }

  buildAppBar() {
    return AppBar(
      backgroundColor: themeConfig.appBarBg,
      title: Row(
        children: [Icon(Icons.lock), Text(" Lås timmar")],
      ),
    );
  }

  buildBody() {
    return Stack(
      children: [buildControlsLayer(), buildLoadDialogLayer()],
    );
  }

  buildControlsLayer() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      mainAxisSize: MainAxisSize.max,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [buildTopSelectBar(), buildTimelogList()],
    );
  }

  buildLoadDialogLayer() {
    return Container();
  }

  buildTimelogList() {
    return Expanded(
        flex: 100,
        child: Scrollbar(
            child: ListView(
          children: buildTimelogBoxes(),
        )));
  }

  buildTimelogBoxes() {
    var boxDatas = TimelogBoxDataHelper.createList(unlockedLogs, workOrders, tasks, projects);

    var widgets = new List<Widget>();
    timelogBoxes.clear();
    for (var i = 0; i < boxDatas.length; i++) {
      var box = buildTimelogBox(boxDatas[i], checkboxes[i], i);
      widgets.add(box);
      timelogBoxes.add(box);
    }
    this.boxDatas = boxDatas;
    return widgets;
  }

  LockTimelogBox buildTimelogBox(TimelogBoxData boxData, bool isChecked, int listIndex) {
    var box = new LockTimelogBox(
      data: boxData,
      giveSelectFunction: addSelectFunction,
      giveDeselectFunction: addDeselectFunction,
      isChecked: isChecked,
      checkChangeCallback: checkChangeCallback,
      listIndex: listIndex,
    );
    return box;
  }

  void initValues() {
    checkboxes = [];
    boxDatas = [];
    displayHeight = 1;
    displayWidth = 1;
    unlockedLogs = [];
    projects = [];
    workOrders = [];
    tasks = [];
    selectAllOptionActive = true;
    timelogBoxes = [];
    selectFunctions = [];
    deselectFunctions = [];
  }

  Future loadInitData() async {
    await loadTimelogs();
    if (unlockedLogs.length > 0) await loadProjectData();
  }

  void initServices() {
    projectHttpService = new ProjectHttpService();
    workOrderHttpService = new WorkOrderHttpService();
    taskHttpService = new TaskHttpService();
    timeLogHttpService = new TimeLogHttpService();
  }

  void onLoadTimelogsError() {
    MessageDialogHelper.show("Fel vid laddning av tidsloggar", "Fel uppstod vid laddning av tidsloggar.", context);
  }

  void onLoadTimelogsSuccess(Response response) {
    var timelogs = TimelogHelper.getFromJson(response.body);
    setTimelogs(timelogs);
  }

  setTimelogs(List<TimeLogModel> timelogs) {
    setState(() {
      // temp
      timelogs.forEach((log) {
        checkboxes.add(false);
      });
      // -

      unlockedLogs = timelogs;
    });
  }

  Future loadTimelogs() async {
    try {
      var response = await timeLogHttpService.getUnlockedLogs(globals.userId);
      if (HttpHelper.isSuccess(response))
        onLoadTimelogsSuccess(response);
      else
        onLoadTimelogsError();
    } catch (e) {
      onLoadTimelogsError();
    }
  }

  Future loadProjectData() async {
    var futures = new List<Future>();
    futures.add(loadProjects(unlockedLogs));
    futures.add(loadWorkOrders(unlockedLogs));
    futures.add(loadTasks(unlockedLogs));
    var results = await Future.wait(futures);
    var projects = getFromResults<ProjectModel>(results);
    var workOrders = getFromResults<WorkOrderModel>(results);
    var tasks = getFromResults<TaskModel>(results);
    setProjects(projects);
    setWorkOrders(workOrders);
    setTasks(tasks);
  }

  Future<List<ProjectModel>> loadProjects(List<TimeLogModel> timelogs) async {
    var futures = new List<Future<ProjectModel>>();
    var projectIds = TimelogHelper.getProjectIds(timelogs);
    projectIds.forEach((id) {
      futures.add(loadProject(id));
    });
    var projects = await Future.wait(futures);
    return projects;
  }

  Future<ProjectModel> loadProject(String id) async {
    try {
      var response = projectHttpService.getProject(id);
      return response.then<ProjectModel>((resp) {
        if (HttpHelper.isSuccess(resp))
          return ProjectHelper.getSingleFromJson(resp.body, true);
        else
          return null;
      }).catchError((err) {
        return null;
      });
    } catch (e) {
      return null;
    }
  }

  Future<List<WorkOrderModel>> loadWorkOrders(List<TimeLogModel> timelogs) async {
    var futures = new List<Future<WorkOrderModel>>();
    var workOrderIds = WorkOrderHelper.getWorkOrderIds(timelogs);
    workOrderIds.forEach((id) {
      futures.add(loadWorkOrder(id));
    });
    var workOrders = await Future.wait(futures);
    return workOrders;
  }

  Future<WorkOrderModel> loadWorkOrder(String id) async {
    try {
      var response = workOrderHttpService.get(id);
      return response.then<WorkOrderModel>((resp) {
        if (HttpHelper.isSuccess(resp)) {
          var list = WorkOrderHelper.getSingleFromJson(resp.body, true);
          return list;
        } else
          return null;
      }).catchError((err) {
        return null;
      });
    } catch (e) {
      return null;
    }
  }

  Future<List<TaskModel>> loadTasks(List<TimeLogModel> timelogs) async {
    var futures = new List<Future<TaskModel>>();
    var taskIds = TaskHelper.getTaskIds(timelogs);
    taskIds.forEach((id) {
      futures.add(loadTask(id));
    });
    var tasks = await Future.wait(futures);
    return tasks;
  }

  Future<TaskModel> loadTask(String id) async {
    try {
      var response = taskHttpService.getById(id);
      return response.then<TaskModel>((resp) {
        if (HttpHelper.isSuccess(resp)) {
          var list = TaskHelper.getSingleFromJson(resp.body, true);
          return list;
        } else
          return null;
      }).catchError((err) {
        return null;
      });
    } catch (e) {
      return null;
    }
  }

  List<T> getFromResults<T>(List<dynamic> results) {
    List<T> result;
    results.forEach((res) {
      if (res is List<T>) result = res;
    });
    return result;
  }

  setProjects(List<ProjectModel> projects) {
    setState(() {
      this.projects = projects;
    });
  }

  setWorkOrders(List<WorkOrderModel> workOrders) {
    setState(() {
      this.workOrders = workOrders;
    });
  }

  setTasks(List<TaskModel> tasks) {
    setState(() {
      this.tasks = tasks;
    });
  }

  buildTopSelectBar() {
    return Expanded(
        flex: 8,
        child: Container(
          decoration: BoxDecoration(
            border: Border(bottom: BorderSide(width: displayHeight * 0.0005, color: Color(0x77FFFFFF))),
            gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF13313b), Color(0xFF11131a)]),
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.max,
            children: [buildSelectAllButton(), buildSelectCount()],
          ),
        ));
  }

  setDisplayDimensions() {
    if (displayWidth == 1 || displayWidth == null) displayWidth = DisplayHelper.getDisplayWidth(context);
    if (displayHeight == 1 || displayHeight == null) displayHeight = DisplayHelper.getDisplayHeight(context);
  }

  buildSelectAllButton() {
    return Expanded(
        flex: 100,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onSelectAllTap,
            child: Container(
              padding: EdgeInsets.only(left: displayWidth * 0.03),
              alignment: Alignment.center,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  Container(
                    child: Icon(
                      selectAllOptionActive ? Icons.check_box : Icons.cancel,
                      color: Colors.white,
                      size: displayWidth * 0.05,
                    ),
                  ),
                  Container(
                    margin: EdgeInsets.only(left: displayWidth * 0.015),
                    child: Text(
                      selectAllOptionActive ? "Markera alla" : "Avmarkera alla",
                      style: TextStyle(fontSize: displayWidth * 0.05, color: Colors.white),
                    ),
                  )
                ],
              ),
            ),
          ),
        ));
  }

  buildSelectCount() {
    return Expanded(
        flex: 100,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            child: Container(
              alignment: Alignment.center,
              child: Text(
                "",
                style: TextStyle(fontSize: displayWidth * 0.05, color: Colors.white),
              ),
            ),
          ),
        ));
  }

  void onSelectAllTap() {
    setState(() {
      if (selectAllOptionActive)
        selectAll();
      else
        deselectAll();
      setSelectAllOption(!selectAllOptionActive);
    });
  }

  setSelectAllOption(bool selectAllActive) {
    setState(() {
      selectAllOptionActive = selectAllActive;
    });
  }

  void selectAll() {
    print(selectFunctions);
    print(timelogBoxes);
    print(unlockedLogs);
    for (var i = 0; i < checkboxes.length; i++) checkboxes[i] = true;
    selectFunctions.forEach((func) {
      func();
    });
  }

  void deselectAll() {
    for (var i = 0; i < checkboxes.length; i++) checkboxes[i] = false;
    deselectFunctions.forEach((func) {
      func();
    });
  }

  addSelectFunction(Function f) {
    selectFunctions.add(f);
  }

  addDeselectFunction(Function f) {
    deselectFunctions.add(f);
  }

  checkChangeCallback(int listIndex, bool isChecked) {
    setState(() {
      checkboxes[listIndex] = isChecked;
      selectAllOptionActive = !checkboxes.any((b) => b);
    });
  }
}

当前子(框)代码:

class LockTimelogBox extends StatefulWidget {
  final TimelogBoxData data;
  final Function giveSelectFunction;
  final Function giveDeselectFunction;
  final bool isChecked;
  final Function checkChangeCallback;
  final int listIndex;

  LockTimelogBox({this.data, this.giveSelectFunction, this.giveDeselectFunction, this.isChecked, this.checkChangeCallback, this.listIndex});

  @override
  TimeLogBoxState createState() => TimeLogBoxState();
}

class TimeLogBoxState extends State<LockTimelogBox> {
  double displayWidth = 1;
  double displayHeight = 1;
  double boxRowFontsizeFactor;
  bool isChecked;

  @override
  void initState() {
    initValues();
    widget.giveSelectFunction(select);
    widget.giveDeselectFunction(deselect);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    setDisplayDimensions();
    return Container(
      height: displayHeight * 0.2,
      width: displayWidth,
      decoration: BoxDecoration(
          gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF13313b), Color(0xFF11131a)]),
          border: Border(bottom: BorderSide(width: displayHeight * 0.0005, color: Color(0x77FFFFFF)))),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          onTap: onBoxTap,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.max,
            children: [buildCheckBoxContainer(), buildInfoContainer()],
          ),
        ),
      ),
    );
  }

  buildCheckBoxContainer() {
    return Expanded(
        flex: 10,
        child: Container(
          alignment: Alignment.center,
          child: buildCheckbox(),
        ));
  }

  buildInfoContainer() {
    return Expanded(
        flex: 70,
        child: Container(
          padding: EdgeInsets.only(top: displayHeight * 0.005, bottom: displayHeight * 0.005, left: displayWidth * 0.01, right: displayWidth * 0.01),
          child: Column(
            children: [buildDateRow(), buildProjectRow(), buildWorkOrderRow(), buildTaskRow()],
          ),
        ));
  }

  buildCheckbox() {
    return Container(
      child: Theme(
        data: ThemeData(primarySwatch: Colors.green, unselectedWidgetColor: Colors.grey),
        child: Transform.scale(
          scale: 1.5,
          child: Checkbox(
            value: isChecked,
            onChanged: onCheckChange,
          ),
        ),
      ),
    );
  }

  void onCheckChange(bool isChecked) {
    widget.checkChangeCallback(widget.listIndex, isChecked);
    setIsChecked(isChecked);
  }

  setIsChecked(bool isChecked) {
    if (this.mounted) {
      setState(() {
        this.isChecked = isChecked;
      });
    } else
      this.isChecked = isChecked;
  }

  void onBoxTap() {
    onCheckChange(!isChecked);
  }

  buildDateRow() {
    return Expanded(
        child: Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.max,
      children: [
        Container(
          alignment: Alignment.centerLeft,
          child: Icon(
            Icons.access_time,
            color: Colors.white,
          ),
        ),
        Container(
          margin: EdgeInsets.only(left: displayWidth * 0.01),
          alignment: Alignment.centerLeft,
          child: Text(
            DateFormat("yyyy-MM-dd HH:mm").format(widget.data.timeLog.start),
            style: TextStyle(fontSize: displayWidth * boxRowFontsizeFactor, color: Colors.white),
          ),
        )
      ],
    ));
  }

  buildProjectRow() {
    return Expanded(
        child: Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.max,
      children: [
        Container(
          alignment: Alignment.centerLeft,
          child: Icon(
            Icons.work,
            color: Colors.white,
          ),
        ),
        Container(
          margin: EdgeInsets.only(left: displayWidth * 0.01),
          alignment: Alignment.centerLeft,
          child: Text(
            widget.data.timeLog.projectName,
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(fontSize: displayWidth * boxRowFontsizeFactor, color: Colors.white),
          ),
        )
      ],
    ));
  }

  buildWorkOrderRow() {
    return Expanded(
        child: Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.max,
      children: [
        Container(
          alignment: Alignment.centerLeft,
          child: Icon(
            Icons.work,
            color: Colors.white,
          ),
        ),
        Container(
          margin: EdgeInsets.only(left: displayWidth * 0.01),
          alignment: Alignment.centerLeft,
          child: Text(
            widget.data.workOrder.name,
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
            style: TextStyle(fontSize: displayWidth * boxRowFontsizeFactor, color: Colors.white),
          ),
        )
      ],
    ));
  }

  buildTaskRow() {
    return Expanded(
        child: Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.max,
      children: [
        Container(
          alignment: Alignment.centerLeft,
          child: Icon(
            Icons.playlist_add_check,
            color: Colors.white,
          ),
        ),
        Container(
          margin: EdgeInsets.only(left: displayWidth * 0.01),
          alignment: Alignment.centerLeft,
          child: Text(
            widget.data.timeLog.projectName,
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
            style: TextStyle(fontSize: displayWidth * boxRowFontsizeFactor, color: Colors.white),
          ),
        )
      ],
    ));
  }

  void initValues() {
    displayWidth = 1;
    displayHeight = 1;
    boxRowFontsizeFactor = 0.05;
    isChecked = widget.isChecked;
  }

  setDisplayDimensions() {
    if (displayWidth == 1 || displayWidth == null) displayWidth = DisplayHelper.getDisplayWidth(context);
    if (displayHeight == 1 || displayHeight == null) displayHeight = DisplayHelper.getDisplayHeight(context);
  }

  select() {
    setIsChecked(true);
  }

  deselect() {
    setIsChecked(false);
  }
}

【问题讨论】:

  • 您可以使用Stateless list-items 完成此操作。如果你可以使用StatelessWidget,你应该。实现此目的的一种方法是存储List&lt;bool&gt; 并为每个孩子提供一种方法来设置boolList 中的索引处。

标签: flutter checkbox children stateful


【解决方案1】:

你做错了什么改变了应该是不可变的东西,这是一个非常糟糕的想法和做法。

状态类是专门用来处理你的状态变化的,所以你应该做的是在类中声明一个状态变量,因为有多个复选框,初始化一个List&lt;bool&gt; checkBoxes并将它们映射到使用来自widget.data 或者如果它们不多,则为每个变量设置单独的变量,然后在您的 CheckBox() 中使用它们

class TimeLogBoxState extends State<LockTimelogBox> {
...

bool isChecked = widget.data.isChecked;


buildCheckbox() {
    return Container(
      child: Theme(
        data: ThemeData(primarySwatch: Colors.green, unselectedWidgetColor: Colors.grey),
        child: Transform.scale(
          scale: 1.5,
          child: Checkbox(
            value: isChecked,
            onChanged:(val){
              setState((){ isChecked = val});
            },
          ),
        ),
      ),
    );
  }

类似的东西。

【讨论】:

  • 我更新了帖子。它现在几乎可以工作,但并非完全有效。你能再看看吗?谢谢!
  • 好的,现在我按照你所说的创建一个单独的 List 来存储 Checkbox 状态,无论它们是否被选中。现在它起作用了。我会接受您的回答并在我的问题中更新我的代码,以便它可以帮助其他人。谢谢!
猜你喜欢
  • 2020-08-30
  • 1970-01-01
  • 2019-06-08
  • 1970-01-01
  • 2020-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-13
相关资源
最近更新 更多