Flutter学习(8)——CheckBox多选框使用及动态更改多选框数据

Stars-one 2021年08月10日 90次浏览 本篇字数为13,009字

本文为作者原创,转载请注明出处,谢谢配合
作者:Stars-one
链接:https://stars-one.site/2021/08/10/flutter-study-8


最近项目需求需要调整页面,记录一下实现过程

这次主要是要实现个评价页面,选择不同的星级显示不同的多选框数据,加上之前也没有使用过CheckBox,今天便是一起讲吧

本篇代码github

效果预览

效果看似有点复杂,我们一步步来实现吧 😄

1.星级组件使用

首先,我们有使用到星级评分组件pubspec.yaml文件中添加下面依赖

flutter_simple_rating_bar: ^0.0.3

使用的话页面也是有个例子介绍,我们直接拿来即可使用

RatingBar(
		//默认选择5星
		rating: 5,
        //总星星数目
        starCount: 5,
        //星星图标
        icon: Icon(
            Icons.star,
            size: 40,
            color: Colors.grey,
        ),
        spacing: 5.0,
        size: 40,
        isIndicator: false,
        //是否可选半星
        allowHalfRating: true,
        onRatingCallback:(double value, ValueNotifier<bool> isIndicator) {
	        //value是点击后选中的星星数(即评分),范围为1-rating(我们上面rating为5)
            
        },
        color: Colors.amber,
)

现在我们要根据选择⭐的数目不同,从而显示不同的文字,这过程是动态的,所以我们选择使用StatefulWidget作为页面继承的基类

我们先在State类中加上相应的数据,如下所示

//显示的文字信息
List typeList = ["非常不满意", "不满意", "基本满意", "满意", "非常满意"];

//当前选择type的下标
int selectType = 4;

我们文字显示内容是typeList[selectType],我们在星级评分选择的回调函数,更改selecType即可实现更改UI的功能

Column(
    children: [
        RatingBar(
            rating: 5,
            //默认选择5星
            starCount: 5,
            //总星星数目
            //星星图标
            icon: Icon(
                Icons.star,
                size: 40,
                color: Colors.grey,
            ),
            spacing: 5.0,
            size: 40,
            isIndicator: false,
            //是否可选半星
            allowHalfRating: true,
            onRatingCallback:
            (double value, ValueNotifier<bool> isIndicator) {
                //更改数据同时也要更改UI,所以使用setState方法
                setState(() {
                    //注意 星级从1开始,selectType是下标,从0开始,需要减1
                    selectType = value.toInt() - 1;
                });
            },
            color: Colors.amber,
        ),
        //取指定下标的文字内容展示
        Text(typeList[selectType])
    ],
)
    

2.多选框使用及数据更改

CheckBox多选框的使用需要定义一个bool类型的数据,每次点击需要更改此数据来改变CheckBox的选择状态

由于我们是使用的列表数据,所以每个数据写个变量不太现实,而且繁琐,所以可以考虑在实体类中加个bool类型的字段,用来存储CheckBox选择的状态

这里,数据我就暂且写死,实际上是要通过调用接口获取的

void initTagData() {
    var data = """{
    "code":200,
    "error":"",
    "ReturnValue":[{"generalstars":"1","tagid":"109759610348409856","tagname":"无理由超过法定办理时间","updatetime":"2020-06-12 17:38:00"},{"generalstars":"1","tagid":"6176177900787350","tagname":"在办事指南之外增加新的审批条件","updatetime":"2020-08-27 00:00:00"},{"generalstars":"1","tagid":"6176177900787351","tagname":"需提供办事指南之外的申报材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"1","tagid":"7176177890787535","tagname":"无理由超过法定办理时间","updatetime":"2020-08-27 00:00:00"},{"generalstars":"2","tagid":"6176177900787349","tagname":"未在承诺时间内办结","updatetime":"2020-08-27 00:00:00"},{"generalstars":"2","tagid":"6176177900787347","tagname":"没有提供材料样本","updatetime":"2020-08-27 00:00:00"},{"generalstars":"2","tagid":"6176177900787348","tagname":"没有提供材料清单","updatetime":"2020-08-27 00:00:00"},{"generalstars":"3","tagid":"119596473292723200","tagname":"22222","updatetime":"2020-06-12 17:38:00"},{"generalstars":"3","tagid":"6176177900787346","tagname":"在承诺的时间内办结","updatetime":"2020-08-27 00:00:00"},{"generalstars":"3","tagid":"6176177900787345","tagname":"提供申报材料样本","updatetime":"2020-08-27 00:00:00"},{"generalstars":"3","tagid":"6176177900787344","tagname":"一次性告知需要补正的材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"4","tagid":"6176177900787340","tagname":"填写一张表单就可以完成申报","updatetime":"2020-08-27 00:00:00"},{"generalstars":"4","tagid":"6176177900787341","tagname":"在线提交材料窗口核验","updatetime":"2020-08-27 00:00:00"},{"generalstars":"4","tagid":"6176177900787342","tagname":"一张清单告知全部申报材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"5","tagid":"7176177890787335","tagname":"一窗受理一次办结","updatetime":"2020-08-27 00:00:00"},{"generalstars":"5","tagid":"7176177890787235","tagname":"可以先受理后补材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"5","tagid":"7176177890787435","tagname":"不用提交证明","updatetime":"2020-08-27 00:00:00"}]
    }""";
    Map<String, dynamic> responseData = jsonDecode(data);
    var tagListModel = TagListModel.fromJson(responseData);
    if (tagListModel.code == 200) {
      //注意要使用setState,才会使UI发生变化(因为网络请求是耗时任务,可能界面渲染完毕后才能请求到数据)
      setState(() {
        tagList.addAll(tagListModel.tagItems);
      });
    }
  }

TagListModel是生成的实体类,生成的步骤已经在上篇文章讲解到,这里就不在赘述了

按照上面说的思路,我们在TagListModel的实体类中**(其实是在TagItem中)**,添加一个bool类型的字段,用来作为存储CheckBox选择的状态,

这里使用插件生成的实体类(TagListModel里面还包含有个实体类),原本应该是ReturnValue,但为了方便,我将其改了个名字TagItem

Flutter中的CheckBox是只有一个框,并不包含文字,所以我们得组合一个文本,拼成一个Widget,_tagView()这个方法就是每个选项

Widget _tagView(item) {
    var str = item.tagname;
    return InkWell(
        child: Row(
            children: <Widget>[
                Checkbox(
                    value: item.flag,
                    activeColor: Colors.blue,
                    onChanged: (bool val) {
                        // val 是布尔值
                        this.setState(() {
                            item.flag = val;
                        });
                    },
                ),
                Text(str),
            ],
        ),
        onTap: () {
            setState(() {
                item.flag = !item.flag;
            });
        },
   );
          

这里最外层使用了InkWell,是因为其有onTag的点击事件方法 点击整行都可以去改变CheckBox的状态,用户体验比较友好,如果没有的话,只能点击CheckBox的框来改变状态

有了上面的一个子项,现在我们需要构建一个多选选项列表(列表包含N个_tagView组件)

先声明两个数组,用来存放数据

//标签数据(存放所有数据)
List<TagItem> tagList = [];

//临时存放的数据,后面评价提交需要
List<TagItem> tempTagList = [];

这里为什么需要两个数组呢?

主要是考虑到后面需要获取勾选的数据,所以才考虑再多加一个临时的列表

每次改变星级的时候,列表选项的数据就会改变,之后我们只需要去这个临时列表中筛选状态为true的数据即可

List<Widget> _tagList() {
    List<Widget> widgetList = [];
    var list = this.tagList;
    tempTagList.clear();

    var star = selectType.toInt() + 1;
    //匹配相同星级数据
    for (var i = 0; i < list.length; i++) {
      var item = list[i];
      if (item.generalstars == star.toString()) {
        //存在临时表中,之后可以快速查找用户的选择
        tempTagList.add(item);
        var row = _tagView(item);
        widgetList.add(row);
      }
    }
    return widgetList;
}

由于数据较少,可以使用Column用来包裹上面的组件_tagList

//标签选项
Container(
    decoration: BoxDecoration(color: Colors.white),
    padding: EdgeInsets.fromLTRB(10, 0, 15, 5),
    child: Column(
    	children: _tagList(),
    )
)

由于提交标签需要传标签的id,而且需要逗号隔开,所以需要遍历临时选项列表,处理一下即可

//处理标签
var tagId = "";
for (var i = 0; i < tempTagList.length; i++) {
    var item = tempTagList[i];
    //查找选择的标签数据
    if (item.flag) {
        tagId += item.tagid + ",";
    }
}
if (tagId.length>0) {
	tagId = tagId.substring(0, tagId.length - 1);
}
print(tagId);

源码

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_demo/model/tag_list_model.dart';
import 'package:flutter_simple_rating_bar/flutter_simple_rating_bar.dart';

class CheckBoxPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new CheckBoxState();
  }
}

class CheckBoxState extends State<CheckBoxPage> {
  //标签数据
  List<TagItem> tagList = [];

  //临时标签,后面评价提交需要
  List<TagItem> tempTagList = [];

  List typeList = ["非常不满意", "不满意", "基本满意", "满意", "非常满意"];

  //当前选择type的下标
  int selectType = 4;

  @override
  void initState() {
    //初始化数据(这里是暂时写死,实际中这里是调用接口)
    initTagData();
  }

  void initTagData() {
    var data = """{
    "code":200,
    "error":"",
    "ReturnValue":[{"generalstars":"1","tagid":"109759610348409856","tagname":"无理由超过法定办理时间","updatetime":"2020-06-12 17:38:00"},{"generalstars":"1","tagid":"6176177900787350","tagname":"在办事指南之外增加新的审批条件","updatetime":"2020-08-27 00:00:00"},{"generalstars":"1","tagid":"6176177900787351","tagname":"需提供办事指南之外的申报材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"1","tagid":"7176177890787535","tagname":"无理由超过法定办理时间","updatetime":"2020-08-27 00:00:00"},{"generalstars":"2","tagid":"6176177900787349","tagname":"未在承诺时间内办结","updatetime":"2020-08-27 00:00:00"},{"generalstars":"2","tagid":"6176177900787347","tagname":"没有提供材料样本","updatetime":"2020-08-27 00:00:00"},{"generalstars":"2","tagid":"6176177900787348","tagname":"没有提供材料清单","updatetime":"2020-08-27 00:00:00"},{"generalstars":"3","tagid":"119596473292723200","tagname":"22222","updatetime":"2020-06-12 17:38:00"},{"generalstars":"3","tagid":"6176177900787346","tagname":"在承诺的时间内办结","updatetime":"2020-08-27 00:00:00"},{"generalstars":"3","tagid":"6176177900787345","tagname":"提供申报材料样本","updatetime":"2020-08-27 00:00:00"},{"generalstars":"3","tagid":"6176177900787344","tagname":"一次性告知需要补正的材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"4","tagid":"6176177900787340","tagname":"填写一张表单就可以完成申报","updatetime":"2020-08-27 00:00:00"},{"generalstars":"4","tagid":"6176177900787341","tagname":"在线提交材料窗口核验","updatetime":"2020-08-27 00:00:00"},{"generalstars":"4","tagid":"6176177900787342","tagname":"一张清单告知全部申报材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"5","tagid":"7176177890787335","tagname":"一窗受理一次办结","updatetime":"2020-08-27 00:00:00"},{"generalstars":"5","tagid":"7176177890787235","tagname":"可以先受理后补材料","updatetime":"2020-08-27 00:00:00"},{"generalstars":"5","tagid":"7176177890787435","tagname":"不用提交证明","updatetime":"2020-08-27 00:00:00"}]
    }""";
    Map<String, dynamic> responseData = jsonDecode(data);
    var tagListModel = TagListModel.fromJson(responseData);
    if (tagListModel.code == 200) {
      //注意要使用setState,才会使UI发生变化(因为网络请求是耗时任务,可能界面渲染完毕后才能请求到数据)
      setState(() {
        tagList.addAll(tagListModel.tagItems);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("评价"),
      ),
      body: Column(
        children: [
          Container(
            decoration: BoxDecoration(color: Colors.white),
            padding: EdgeInsets.fromLTRB(15, 0, 15, 10),
            child: Wrap(
              spacing: 10,
              children: [
                Center(
                  child: Column(
                    children: [
                      RatingBar(
                        rating: 5,
                        //默认选择5星
                        starCount: 5,
                        //总星星数目
                        //星星图标
                        icon: Icon(
                          Icons.star,
                          size: 40,
                          color: Colors.grey,
                        ),
                        spacing: 5.0,
                        size: 40,
                        isIndicator: false,
                        //是否可选半星
                        allowHalfRating: true,
                        onRatingCallback:
                            (double value, ValueNotifier<bool> isIndicator) {
                          //更改数据同时也要更改UI,所以使用setState方法
                          setState(() {
                            //注意 星级从1开始,selectType是下标,从0开始,需要减1
                            selectType = value.toInt() - 1;
                          });
                        },
                        color: Colors.amber,
                      ),
                      _tipText()
                    ],
                  ),
                )
              ],
            ),
          ),
          //标签选项
          Container(
              decoration: BoxDecoration(color: Colors.white),
              padding: EdgeInsets.fromLTRB(10, 0, 15, 5),
              child: Column(
                children: _tagList(),
              )),
          Container(
              decoration: BoxDecoration(color: Colors.white),
              padding: EdgeInsets.fromLTRB(10, 0, 15, 5),
              child: FlatButton(
                child: Text("提交"),
                onPressed: () {
                  //处理标签
                  var tagId = "";
                  for (var i = 0; i < tempTagList.length; i++) {
                    var item = tempTagList[i];
                    //查找选择的标签数据
                    if (item.flag) {
                      tagId += item.tagid + ",";
                    }
                  }
                  if (tagId.length>0) {
                    tagId = tagId.substring(0, tagId.length - 1);
                  }
                  print(tagId);
                },
              )),
        ],
      ),
    );
  }

  Widget _tipText() {
    //根据selectType变更UI
    return Text(typeList[selectType]);
  }

  List<Widget> _tagList() {
    List<Widget> widgetList = [];
    var list = this.tagList;
    tempTagList.clear();

    var star = selectType.toInt() + 1;
    for (var i = 0; i < list.length; i++) {
      var item = list[i];
      if (item.generalstars == star.toString()) {
        //存在临时表中,之后可以快速查找用户的选择
        tempTagList.add(item);
        var row = _tagView(item);
        widgetList.add(row);
      }
    }
    return widgetList;
  }

  Widget _tagView(item) {
    var str = item.tagname;
    return InkWell(
      child: Row(
        children: <Widget>[
          Checkbox(
            value: item.flag,
            activeColor: Colors.blue,
            onChanged: (bool val) {
              // val 是布尔值
              this.setState(() {
                item.flag = val;
              });
            },
          ),
          Text(str),
        ],
      ),
      onTap: () {
        setState(() {
          item.flag = !item.flag;
        });
      },
    );
  }
}

相关标签