原文地址:Flutter學習(8)——CheckBox多選框使用及動態更改多選框資料 | Stars-One的雜貨小窩
最近專案需求需要調整頁面,記錄一下實現過程
這次主要是要實現個評價頁面,選擇不同的星級顯示不同的多選框資料,加上之前也沒有使用過CheckBox,今天便是一起講吧
效果預覽
效果看似有點複雜,我們一步步來實現吧 ?
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;
});
},
);
}
}