项目背景:
近期Flutter项目有个需求,根据后端传过来的数据,动态生成检查项目卡片,卡片中动态生成5个检查结果录入的TextField和一个结果判定的TextField,结果判定的TextField中的内容根据5个检查结果自动判定OK和NG,并将OK背景色设置为绿色,NG背景色设置为红色。通过控制软键盘的回车键自动跳转到下一个TextField,最后一个TextField则关闭软键盘。
代码:
/// @Author JackMa
/// @Date 2023/7/6 11:56
///
/// @Description 初物检查界面
import 'package:flutter/material.dart';
class PrimerCheckContent extends StatefulWidget {
static String routeName = "PrimerCheck";
PrimerCheckContent({Key? key})
: super(key: key);
@override
State<PrimerCheckContent> createState() => _PrimerCheckContentState();
}
class _PrimerCheckContentState extends State<PrimerCheckContent> {
//初物检查项目
List<Map<String, dynamic>> checkItem = [];
//最后一个焦点对象,用以控制软键盘关闭
FocusNode? lastFocusNode;
@override
void initState() {
// TODO: implement initState
super.initState();
// 实际项目从后端获取数据
checkItem.add(
{
"insItem": "项目名称",
"insCondition": "检查规格",
"max": "10",
"min": "0",
"insUnit": "测量单位",
//五个检查录入格子控制器
"textEditControllers": [
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
],
//五个检查录入格子的状态,用来推断OK/NG
"textEditStatus": [
false,
false,
false,
false,
false,
],
//五个检查录入格子焦点
"focusNodes": [
new FocusNode(),
new FocusNode(),
new FocusNode(),
new FocusNode(),
new FocusNode(),
],
//判定格子的控制器
"judgeController": new TextEditingController(),
//通过来回切换true/false来触发组件刷新,防止键盘弹起时无法即使更新TextField的背景色
"refresh": ValueNotifier<bool>(true)
},
);
checkItem.add(
{
"insItem": "项目名称",
"insCondition": "检查规格",
"max": "10",
"min": "",
"insUnit": "测量单位",
"textEditControllers": [
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
],
"textEditStatus": [
false,
false,
false,
false,
false,
],
"focusNodes": [
new FocusNode(),
new FocusNode(),
new FocusNode(),
new FocusNode(),
new FocusNode(),
],
"judgeController": new TextEditingController(),
"refresh": ValueNotifier<bool>(true)
},
);
checkItem.add(
{
"insItem": "项目名称",
"insCondition": "检查规格",
"max": "",
"min": "0",
"insUnit": "测量单位",
"textEditControllers": [
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
new TextEditingController(),
],
"focusNodes": [
new FocusNode(),
new FocusNode(),
new FocusNode(),
new FocusNode(),
new FocusNode(),
],
"textEditStatus": [
false,
false,
false,
false,
false,
],
"judgeController": new TextEditingController(),
"refresh": ValueNotifier<bool>(true)
},
);
lastFocusNode = checkItem[2]["focusNodes"][4];
}
@override
void dispose() {
// 将焦点控制对象释放
for (var item in checkItem) {
for (var focusNode in item["focusNodes"]) {
focusNode.dispose();
}
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: "动态生成TextField",
),
body: Container(
child: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(left: ScreenAdapter.width(5)),
height: ScreenAdapter.height(45),
child: Row(
children: [
Expanded(
child: itemTile(
"生产品番:",
"123123456456",
leadingFontSize: ScreenAdapter.sp(25),
titleFontSize: ScreenAdapter.sp(25),
strutLeading: 2.0,
),
),
SizedBox(
width: 120.0,
),
Expanded(
child: itemTile(
"本体型号:",
"",
leadingFontSize: ScreenAdapter.sp(25),
titleFontSize: ScreenAdapter.sp(25),
strutLeading: 2.0,
),
),
SizedBox(
width: 120.0,
),
Expanded(
child: itemTile(
"额定电压:",
"",
leadingFontSize: ScreenAdapter.sp(25),
titleFontSize: ScreenAdapter.sp(25),
strutLeading: 2.0,
),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: checkItem.length,
itemBuilder: (ctx, index) {
return checkItemCard(checkItem[index]);
},
),
),
],
),
),
);
}
Widget checkItemCard(var item) {
/// description 检查项目卡片
/// author JackMa
/// Date 2023/7/7 18:18
///
/// [item:检查项目对象]
var fontSize = ScreenAdapter.sp(25);
return Card(
// 阴影颜色
shadowColor: Colors.black,
// 阴影高度
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: Colors.grey,
width: 2,
),
),
child: Container(
width: ScreenAdapter.width(1025),
height: ScreenAdapter.height(280),
padding: EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
Row(
children: [
Expanded(
child: itemTile(
"项目名称:",
item["insItem"],
leadingFontSize: fontSize,
titleFontSize: fontSize,
strutLeading: 2.0,
),
),
SizedBox(
width: 120.0,
),
Expanded(
child: itemTile(
"检查规格:",
item["insCondition"],
leadingFontSize: fontSize,
titleFontSize: fontSize,
strutLeading: 2.0,
),
),
],
),
Row(
children: [
Expanded(
child: itemTile(
"规格上限:",
item["max"],
leadingFontSize: fontSize,
titleFontSize: fontSize,
strutLeading: 2.0,
),
),
SizedBox(
width: 120.0,
),
Expanded(
child: itemTile(
"规格下限:",
item["min"],
leadingFontSize: fontSize,
titleFontSize: fontSize,
strutLeading: 2.0,
),
),
SizedBox(
width: 120.0,
),
Expanded(
child: itemTile(
"单位:",
item["insUnit"],
leadingFontSize: fontSize,
titleFontSize: fontSize,
strutLeading: 2.0,
),
),
],
),
judgeWidget(item),
SizedBox(
height: 10,
),
ValueListenableBuilder(
valueListenable: item["refresh"],
builder: (context, value, widget) {
return Row(
children: [
Text(
"判定:",
style: TextStyle(
fontSize: fontSize,
),
),
Expanded(
child: TextField(
textAlign: TextAlign.center,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
fillColor: item["judgeController"].text == "NG"
? Colors.red
: item["judgeController"].text == "OK"
? Color.fromRGBO(103, 194, 58, 1)
: Colors.white,
filled: true,
),
style: TextStyle(
fontSize: fontSize,
),
controller: item["judgeController"],
enabled: false,
),
),
Expanded(
child: Container(),
flex: 4,
)
],
);
})
],
),
),
);
}
/// description 检查录入单元格组件
/// author JackMa
/// Date 2023/7/7 11:03
/// 动态检查录入输入框
judgeWidget(var item) {
List<Widget> textfieldWidgetList = [];
for (int i = 0; i < item["textEditControllers"].length; i++) {
textfieldWidgetList.add(
Expanded(
child: StatefulBuilder(
builder: (context, setInnerState) => TextField(
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
hintText: '请填写',
),
focusNode: item["focusNodes"][i],
//右下角键盘文字:下一项
textInputAction: TextInputAction.next,
style: TextStyle(
fontSize: ScreenAdapter.sp(25),
color: item["textEditStatus"][i] ? Colors.red : Colors.black),
controller: item["textEditControllers"][i],
onEditingComplete: () {
try {
double inputNum =
double.parse(item["textEditControllers"][i].text);
setInnerState(() {
if (item["max"] != "" && item["min"] != "") {
item["textEditStatus"][i] =
inputNum >= double.parse(item["min"]) &&
inputNum <= double.parse(item["max"])
? false
: true;
} else if (item["max"] != "") {
item["textEditStatus"][i] =
inputNum <= double.parse(item["max"]) ? false : true;
} else if (item["min"] != "") {
item["textEditStatus"][i] =
inputNum >= double.parse(item["min"]) ? false : true;
}
});
changeStatus(item, setInnerState);
if (item["focusNodes"][i] != lastFocusNode) {
FocusScope.of(context).nextFocus();
} else {
item["focusNodes"][i].unfocus();
}
} catch (e) {
showToast("只能输入纯数字!");
item["textEditControllers"][i].text = "";
item["textEditStatus"][i] = false;
changeStatus(item, setInnerState);
}
},
),
),
),
);
textfieldWidgetList.add(
const SizedBox(
width: 10,
),
);
}
textfieldWidgetList.removeLast();
return Row(
children: textfieldWidgetList,
);
}
/// description 根据检查结果修改判定状态
/// author JackMa
/// Date 2023/7/10 10:26
changeStatus(var item, setInnerState) {
//是否有异常
bool flag = false;
for (int i = 0; i < item["textEditStatus"].length; i++) {
if (item["textEditStatus"][i]) {
setInnerState(() {
item["judgeController"].text = "NG";
item["refresh"].value = !item["refresh"].value;
});
flag = true;
break;
}
}
if (!flag) {
//是否有空的判定格子
bool hasEmpty = false;
for (int i = 0; i < item["textEditControllers"].length; i++) {
if (item["textEditControllers"][i].text == "") {
setInnerState(() {
item["judgeController"].text = "";
item["refresh"].value = !item["refresh"].value;
});
hasEmpty = true;
break;
}
}
if (!hasEmpty) {
setInnerState(() {
item["judgeController"].text = "OK";
item["refresh"].value = !item["refresh"].value;
});
}
}
}
}
效果图: