Flutter 实现多选弹窗

作者: 岛上码农 | 来源:发表于2021-11-28 21:34 被阅读0次

    Flutter 底部弹窗详解一篇中介绍了底部弹窗的实现。发出后有在琢磨如何实现多选,这也是很常用的一个功能。本篇介绍实现多选的思路和实现方式。

    屏幕录制2021-06-09 下午10.57.51.gif

    多选和单选的不同之处

    单选的时候,选中一个就可以直接把结果返回,因此本身底部弹窗无需状态管理。但到多选的时候,需要知道当前选中的选项,有选项被点击的时候需要存储下来,当再次被点击的时候要清空这个选项,同时界面还需要同步更新,因此就涉及到状态管理了。

    实现方式

    在Flutter 中提供了一个 StatefulBuilder 的类,提供了一个 builder方法构建有状态组件,并且提供了状态更新方法,因此在里面完成状态管理。

    StatefulBuilder(builder: (context1, setState) {
        return Widget;
        }
    )
    

    在这个 builder 方法中,setState 其实就是对应状态组件的setState 对应的方法,这个 state 就是用于控制 StatefulBuilder 生成的组件的状态的。这种方式有点类似于 ReactuseState 的钩子函数用法。

    界面变更

    首先底部弹窗的头部组件要更换,需要增加确认按钮,将构建该组件的方法抽离出来如下所示:

    Widget _getModalSheetHeaderWithConfirm(String title,
          {Function onCancel, Function onConfirm}) {
      return SizedBox(
        height: 50,
        child: Row(
          children: [
            IconButton(
              icon: Icon(Icons.close),
              onPressed: () {
                onCancel();
              },
            ),
            Expanded(
              child: Center(
                child: Text(
                  title,
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0),
                ),
              ),
            ),
            IconButton(
                icon: Icon(
                  Icons.check,
                  color: Colors.blue,
                ),
                onPressed: () {
                  onConfirm();
                }),
          ],
        ),
      );
    }
    

    通过这个方法可以通过外部参数传入标题,取消响应事件回调和确认事件回调,通用性更强。

    其次是选项需要有图标标记,选中时显示为勾选框,未选中时是空白框,这需要通过状态数据来控制。这里我们使用了 Set 类型,保证选中的数据集是不重复的。在点击选项时,如果选项对应数组的下标在 Set 内,则从中移出,表示取消选择;如果不在 Set内,则加入其中,表示选中。这个过程需要包在 state 里,以更新界面。通过列表元素当前的下标是否在 Set 内,如果在则显示为选中,不在则显示未选中。

    最后是确认事件的回调,确认后将 Set 的元素转换为数组返回,然后供上级业务使用选中的下标数组判断选择了那些数据。

    代码实现

    关键代码实现如下,重点关注一下StatefulBuilder的使用和利用 Set 这一数据类型对应的选择和取消选择的操作业务逻辑。

    Future<List<int>> _showMultiChoiceModalBottomSheet(
          BuildContext context, List<String> options) async {
      Set<int> selected = Set<int>();
      return showModalBottomSheet<List<int>>(
        backgroundColor: Colors.transparent,
        isScrollControlled: true,
        context: context,
        builder: (BuildContext context) {
          return StatefulBuilder(builder: (context1, setState) {
            return Container(
              clipBehavior: Clip.antiAlias,
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.only(
                  topLeft: const Radius.circular(20.0),
                  topRight: const Radius.circular(20.0),
                ),
              ),
              height: MediaQuery.of(context).size.height / 2.0,
              child: Column(children: [
                _getModalSheetHeaderWithConfirm('多选底部弹窗', 
                    onCancel: () {
                      Navigator.of(context).pop();
                    }, 
                    onConfirm: () {
                      Navigator.of(context).pop(selected.toList());
                    },
                ),
                Divider(height: 1.0),
                Expanded(
                  child: ListView.builder(
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(
                        trailing: Icon(
                            selected.contains(index)
                                ? Icons.check_box
                                : Icons.check_box_outline_blank,
                            color: Theme.of(context).primaryColor),
                        title: Text(options[index]),
                        onTap: () {
                          setState(() {
                            if (selected.contains(index)) {
                              selected.remove(index);
                            } else {
                              selected.add(index);
                            }
                          });
                        },
                      );
                    },
                    itemCount: options.length,
                  ),
                ),
              ]),
            );
          });
        },
      );
    }
    

    总结

    本篇介绍了底部弹窗实现多选的方式,其中实现的方式还可以有很多种,例如直接在自定义组件中使用有状态组件。这里介绍的方法可以作为一个参考,通过动态构建有状态组件能够简单快速地实现底部弹窗的多选功能。

    相关文章

      网友评论

        本文标题:Flutter 实现多选弹窗

        本文链接:https://www.haomeiwen.com/subject/dewhxrtx.html