下面是我的工作代码的最小复制,其中创建了一个动态列表。该列表已在开始时使用单个元素进行初始化。用户可以在按下按钮时添加更多项目,或者用户可以通过从末尾滑动到开始来关闭该项目。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late List<InvoiceItemInput> itemsList;
@override
void initState() {
itemsList = List<InvoiceItemInput>.from([
InvoiceItemInput(
parentWidth: 400.0,
index: 1,
key: ValueKey('1' + DateTime.now().toString()),
)
]);
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) => Dismissible(
key: ValueKey(index.toString() + DateTime.now().toString()),
child: itemsList[index],
background: Container(color: Colors.red),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
if (direction == DismissDirection.endToStart) {
setState(() {
itemsList.removeAt(index);
});
}
}),
itemCount: itemsList.length,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.0),
child: Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
child: Text('ADD'),
onPressed: () {
setState(() {
final int index = itemsList.length;
itemsList.add(InvoiceItemInput(
parentWidth: 400.0,
index: index + 1,
key: ValueKey(
index.toString() + DateTime.now().toString()),
));
});
}),
),
),
],
),
),
),
);
}
}
class InvoiceItemInput extends StatefulWidget {
const InvoiceItemInput(
{super.key, required this.parentWidth, required this.index});
final double parentWidth;
final int index;
@override
State<InvoiceItemInput> createState() => _InvoiceItemInputState();
}
class _InvoiceItemInputState extends State<InvoiceItemInput> {
late TextEditingController? itemController;
final double horizontalSpacing = 15.0;
bool showDeleteButton = false;
@override
void initState() {
itemController = TextEditingController();
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12.0),
Container(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 7.0),
child: Text('Item ${widget.index}',
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(color: Colors.white)),
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(7.0)),
),
SizedBox(height: 7.0),
Wrap(
spacing: this.horizontalSpacing,
runSpacing: 25.0,
children: [
SizedBox(
width: (widget.parentWidth - horizontalSpacing) / 2 < 200.0
? (widget.parentWidth - horizontalSpacing) / 2
: 200.0,
child: DropdownMenu(
controller: itemController,
label: Text(
'Item Name *',
style: const TextStyle(
fontFamily: 'Raleway',
fontSize: 14.0,
color: Colors.black87,
fontWeight: FontWeight.w500),
),
hintText: 'Enter Item Name',
requestFocusOnTap: true,
enableFilter: true,
expandedInsets: EdgeInsets.zero,
textStyle: Theme.of(context).textTheme.bodySmall,
menuStyle: MenuStyle(
backgroundColor: WidgetStateProperty.all(Colors.lightBlue),
),
dropdownMenuEntries: [
DropdownMenuEntry(
value: 'Pen',
label: 'Pen',
style: MenuItemButton.styleFrom(
foregroundColor: Colors.white,
textStyle: Theme.of(context).textTheme.bodySmall,
),
),
DropdownMenuEntry(
value: 'Pencil',
label: 'Pencil',
style: MenuItemButton.styleFrom(
foregroundColor: Colors.white,
textStyle: Theme.of(context).textTheme.bodySmall,
),
)
],
),
),
],
),
SizedBox(height: 15.0),
],
);
}
}
问题: 添加或取消项目时,文本字段/下拉菜单中的更改将丢失。
寻找: 您能否建议一种方法,以便列表项顶部容器中显示的项目
index
也更新为新值,但保持用户输入完整。
每次刷新状态时,都会重新创建 InvoiceItemInputs。当然,您在内部定义的 TextEditingController 也会被重新创建。这似乎是问题的根源。
记住:setState方法会触发关联StatefulWidget的build方法。
在这种情况下,如果你想防止数据丢失,你应该在外部定义控制器。
下面,我将提供一个建议:
class _MyAppState extends State<MyApp> {
late List<InvoiceItemInput> itemsList;
List<TextEditingController> itemControllers = [];
@override
void initState() {
super.initState();
itemsList = List<InvoiceItemInput>.from([
InvoiceItemInput(
parentWidth: 400.0,
index: 1,
controller: TextEditingController(),
key: ValueKey('1' + DateTime.now().toString()),
)
]);
itemControllers.add(TextEditingController());
}
@override
void dispose() {
for (var controller in itemControllers) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) => Dismissible(
key: ValueKey(index.toString() + DateTime.now().toString()),
child: itemsList[index],
background: Container(color: Colors.red),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
if (direction == DismissDirection.endToStart) {
setState(() {
itemsList.removeAt(index);
itemControllers.removeAt(index);
});
}
}),
itemCount: itemsList.length,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.0),
child: Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
child: Text('ADD'),
onPressed: () {
setState(() {
final int index = itemsList.length;
var controller = TextEditingController();
itemControllers.add(controller);
itemsList.add(InvoiceItemInput(
parentWidth: 400.0,
index: index + 1,
controller: controller,
key: ValueKey(
index.toString() + DateTime.now().toString()),
));
});
}),
),
),
],
),
),
),
);
}
}
class InvoiceItemInput extends StatefulWidget {
const InvoiceItemInput(
{super.key,
required this.parentWidth,
required this.index,
required this.controller});
final double parentWidth;
final int index;
final TextEditingController controller;
@override
State<InvoiceItemInput> createState() => _InvoiceItemInputState();
}
class _InvoiceItemInputState extends State<InvoiceItemInput> {
final double horizontalSpacing = 15.0;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12.0),
Container(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 7.0),
child: Text('Item ${widget.index}',
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(color: Colors.white)),
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(7.0)),
),
SizedBox(height: 7.0),
Wrap(
spacing: this.horizontalSpacing,
runSpacing: 25.0,
children: [
SizedBox(
width: (widget.parentWidth - horizontalSpacing) / 2 < 200.0
? (widget.parentWidth - horizontalSpacing) / 2
: 200.0,
child: TextField(
controller: widget.controller,
decoration: InputDecoration(
labelText: 'Item Name *',
hintText: 'Enter Item Name',
),
),
),
],
),
SizedBox(height: 15.0),
],
);
}
}