首页 > 解决方案 > 如何在 Flutter 中选中/取消选中有状态子 WIdget 中的复选框?

问题描述

我有一个名为 的父 Widget ,LockTimelogsList其中包含一个ListView. LockTimelogBox每个子小部件/LockTimelogBox包含一个Checkbox我需要从父更新LockTimelogsList。当用户按下“全选”或“取消全选”时,LockTimelogsList需要迭代LockTimelogBoxes并设置每个内部的复选框为真/假。

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

我尝试了两种方法:

在此处输入图像描述

更新

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

[![在此处输入图像描述][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);
  }
}

标签: fluttercheckboxchildrenstateful

解决方案


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

状态类专门用于处理您的状态更改,因此您应该做的是在类中声明一个状态变量,并且由于有多个复选框,初始化 aList<bool> 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});
            },
          ),
        ),
      ),
    );
  }

类似的东西。


推荐阅读