Flutter中的DropDownButton未显示所选值
我现在在学习Flutter,我已经用 DropDownButton 小部件加载了它的项;不过,尽管按照 这个示例 以及其他类似的回答操作,但每次在 DropDownButton 小部件上选择一个条目时,这个控件/小部件不会显示所选的值,只有在关闭当前打开的ListView时,才能看到已选择/已更改的值。
下面是示例数据:
List<Map<String, dynamic>> tipos_producto = [
{
"ID_TIPO_PRODUCTO": 1,
"NOMBRE_TIPO_PRODUCTO": "Gaseosas",
"DESCRIPCION_TIPO_PRODUCTO": "Bebidas gaseosas",
"ID_TIPO_UNIDAD": 2,
},
{
"ID_TIPO_PRODUCTO": 2,
"NOMBRE_TIPO_PRODUCTO": "Aseo del hogar",
"DESCRIPCION_TIPO_PRODUCTO": "Todo tipo de productos de aseo.",
"ID_TIPO_UNIDAD": 1,
},
{
"ID_TIPO_PRODUCTO": 4,
"NOMBRE_TIPO_PRODUCTO": "Snacks",
"DESCRIPCION_TIPO_PRODUCTO": "Paquetes de papas, entre otros.",
"ID_TIPO_UNIDAD": 1,
},[...]
];
这是该小部件的代码:
import 'package:flutter/material.dart';
import 'package:proyecto_minimarket_apk/tipos_producto.dart';
class opciones extends StatefulWidget {
const opciones({super.key});
@override
State<opciones> createState() => _opcionesState();
}
class _opcionesState extends State<opciones> {
String ddlVlr_tipoProducto = tipos_producto.elementAt(0)["ID_TIPO_PRODUCTO"].toString();
@override
Widget build(BuildContext context) {
// Relevant part: DropDownList added with options:
body: ListView(
children: [
DropdownButton(
hint: Text("Tipo de producto"),
icon: Icon(Icons.arrow_drop_down),
items: tipos_producto.map((map) {
return DropdownMenuItem<String>(
value: map["ID_TIPO_PRODUCTO"].toString(),
child: Text(map["NOMBRE_TIPO_PRODUCTO"],),
);
}).toList(),
value: ddlVlr_tipoProducto,
onChanged: (String? value) {
setState(() {
ddlVlr_tipoProducto = value!;
});
},
),
// [...]
}
}
似乎缺少了什么,但我真的不明白到底缺少了什么——因为调试控制台没有报错,我也查看了 文档,但我无法重现文档中所展示的行为——也就是:如果你选择了一个条目,小部件会显示所选的项(不需要刷新 ListView)。
有人能给我指个正确的方向吗?
编辑:在DartPad上添加了可运行的演示:
- DartPad Gist可运行演示.
- 这段Gist代码在这里 这里 。
复现步骤:
- 运行上面链接的 DartPad Gist可运行示例。
- 选择“Productos”选项。
- 点击屏幕右下角的
ElevatedButton按钮。 - 前两个字段是
DropdownButtons。在它们的DropdownButtons中任意选择一个值。 - 例如,在第一个控件中,默认选择第二个选项 "Aseo del Hogar",所选的元素是 "Gaseosas";但在你再选择另一个值并关闭当前的ListView后,可以再次点击屏幕右下角的
ElevatedButton按钮,你会看到所选的元素是 "Aseo del Hogar"。
解决方案
之所以没有更新,是因为对话框/底部弹出层的状态与当前(单一)小部件的状态是分离的;所以调用setState不会刷新UI; 有几点需要注意
- 试着对类进行适配,而不是map
- 当变得混乱时,尽量将小部件与类分离
在代码中添加了注释,你可以阅读以了解我的意图;尽管那并非最佳实践,但在我专注于仅更新UI的阶段,尽量通过最少修改来解决问题;
这不是最佳实践,只是为了最小的改动来更新UI
import 'dart:math' as math;
import 'package:flutter/material.dart';
const Color primaryColor = Colors.orange;
const TargetPlatform platform = TargetPlatform.android;
/// NONE should be global
List<Map<String, dynamic>> tipos_producto = [
{
"ID_TIPO_PRODUCTO": 1,
"NOMBRE_TIPO_PRODUCTO": "Gaseosas",
"DESCRIPCION_TIPO_PRODUCTO": "Bebidas gaseosas",
"ID_TIPO_UNIDAD": 2,
},
{
"ID_TIPO_PRODUCTO": 2,
"NOMBRE_TIPO_PRODUCTO": "Aseo del hogar",
"DESCRIPCION_TIPO_PRODUCTO": "Todo tipo de productos de aseo.",
"ID_TIPO_UNIDAD": 1,
},
{
"ID_TIPO_PRODUCTO": 3,
"NOMBRE_TIPO_PRODUCTO": "Snacks",
"DESCRIPCION_TIPO_PRODUCTO": "Paquetes de papas, entre otros.",
"ID_TIPO_UNIDAD": 1,
},
];
/// NOPE: GLobal items on setState doesnt work unless you refresh the state of that specific widgets
List<Map<String, dynamic>> productos = [
{
"ID_PRODUCTO": 1,
"ID_TIPO_PRODUCTO": 1,
"ID_MARCA": 4,
"NOMBRE_PRODUCTO": "Postobón gaseosa",
"DESCRIPCION_PRODUCTO": "Postobón 2 litros - sabor manzana.",
"VALOR": 3000,
},
{
"ID_PRODUCTO": 2,
"ID_TIPO_PRODUCTO": 2,
"ID_MARCA": 7,
"NOMBRE_PRODUCTO": "Esponja de brillo",
"DESCRIPCION_PRODUCTO": "Esponja de brillo - bombril - promoción 2x1",
"VALOR": 400,
},
{
"ID_PRODUCTO": 3,
"ID_TIPO_PRODUCTO": 1,
"ID_MARCA": 2,
"NOMBRE_PRODUCTO": "Pepsi",
"DESCRIPCION_PRODUCTO": "Gaseosa Pepsi - 1 litro.",
"VALOR": 2700,
},
{
"ID_PRODUCTO": 4,
"ID_TIPO_PRODUCTO": 2,
"ID_MARCA": 4,
"NOMBRE_PRODUCTO": "Shampoo Head & Shoulders - 200mg",
"DESCRIPCION_PRODUCTO": "Shampoo Head & Shoulders - 200 mg. Promoción 2x1",
"VALOR": 17000,
},
];
List<Map<String, dynamic>> marcas = [
{"ID_MARCA": 1, "NOMBRE_MARCA": "Colgate"},
{"ID_MARCA": 2, "NOMBRE_MARCA": "Guadalupe"},
{"ID_MARCA": 3, "NOMBRE_MARCA": "Aromatel"},
{"ID_MARCA": 4, "NOMBRE_MARCA": "Head & Shoulders"},
{"ID_MARCA": 5, "NOMBRE_MARCA": "Noel"},
{"ID_MARCA": 6, "NOMBRE_MARCA": "Bombril"},
{"ID_MARCA": 7, "NOMBRE_MARCA": "Doritos"},
{"ID_MARCA": 8, "NOMBRE_MARCA": "Advil"},
];
void main() {
//runApp(Sunflower());
runApp(MaterialApp(home: Sunflower()));
}
class Sunflower extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _SunflowerState();
}
}
class _SunflowerState extends State<Sunflower> {
String ddlVlr_tipoProducto = tipos_producto
.elementAt(0)["ID_TIPO_PRODUCTO"]
.toString();
String ddlVlr_marca = marcas.elementAt(0)["ID_MARCA"].toString();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Card(
elevation: 6,
child: ListTile(
leading: Icon(Icons.add_shopping_cart),
title: Text("Productos"),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setStateSB) => Scaffold(
appBar: AppBar(
title: Text('Listado de productos'),
backgroundColor: Color.fromRGBO(0, 120, 215, 1),
foregroundColor: Colors.white,
),
body: ListView.builder(
itemCount: productos.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
leading: Text(
productos[index]['ID_PRODUCTO'].toString(),
),
title: Text(
productos[index]['NOMBRE_PRODUCTO'],
),
subtitle: Text(
tipos_producto[productos[index]['ID_TIPO_PRODUCTO']]['NOMBRE_TIPO_PRODUCTO'],
),
trailing: Text(
productos[index]['VALOR'].toString(),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
backgroundColor: Color.fromRGBO(0, 120, 215, 1),
child: Icon(Icons.add_box_outlined),
onPressed: () async {
await showModalBottomSheet(
context: context,
builder: (context) {
return NewItemCreateView(); // we moved for ....
},
);
/// so we wait ;and refresh the ui
// notice I haved used setStateSB inside StatefulBuilder
setStateSB(() {});
},
),
),
);
},
);
},
),
),
],
),
);
}
}
/// Too much for callback, using class
/// Also try using it for the map as well if fits,else new class;;;
/// OK u are using GLobal List<Map> I am skipping using it; but you should use it
class Product {
Product({
required this.category,
required this.subCategory,
required this.name,
required this.description,
required this.valor,
});
// for dropdown
final String category;
final String subCategory;
final String name;
final String description;
final String valor; //idk what it is
///yap cloning with ,,you can search online
Product copyWith(
String? category,
String? subCategory,
String? name,
String? description,
String? valor,
) {
return Product(
category: category ?? this.category,
subCategory: subCategory ?? this.subCategory,
name: name ?? this.name,
description: description ?? this.description,
valor: valor ?? this.valor,
);
}
}
/// you can always pass through constructor
class NewItemCreateView extends StatefulWidget {
const NewItemCreateView({
super.key,
this.ddlVlr_tipoProducto, // btw we prefer using snakeCase; keeping same
this.ddlVlr_marca,
});
/// just for default initial value
final String? ddlVlr_tipoProducto;
final String? ddlVlr_marca;
@override
State<NewItemCreateView> createState() => _NewItemCreateViewState();
}
class _NewItemCreateViewState extends State<NewItemCreateView> {
late String? ddlVlr_tipoProducto =
widget.ddlVlr_tipoProducto; // get from parent widget
late String? ddlVlr_marca = widget
.ddlVlr_marca; // late is good, but explore initState and didUpdateWidget methods
/// what ever require for form put here;
final TextEditingController nombre_prod_ctrl = TextEditingController(
text: "ok giving default",
);
final TextEditingController descripcion_prod_ctrl = TextEditingController(
text: "ok giving default",
);
final TextEditingController valor_prod_ctrl = TextEditingController(
text: "ok giving default",
);
/// hope this is save(add item)
void _agregarProducto() {
final String nombre = nombre_prod_ctrl.text;
final String descr = descripcion_prod_ctrl.text;
final String valor = valor_prod_ctrl.text;
if (nombre.isNotEmpty && descr.isNotEmpty && valor.isNotEmpty) {
final nuevo_producto = {
"ID_PRODUCTO": productos.length + 1,
"ID_TIPO_PRODUCTO": // TOOO RISKY....try model class instead
int.tryParse(ddlVlr_tipoProducto ?? "") ??
0 - 1, // use tryParse instead of try
"ID_MARCA": // bad idea using `!` but I would say validate it first
int.parse(ddlVlr_marca ?? "0") - 1,
"NOMBRE_PRODUCTO": nombre,
"DESCRIPCION_PRODUCTO": descr,
"VALOR": valor,
};
/// ney.... ok for now just telling parent widget to refresh as well when it pop
setState(() {
productos.add(nuevo_producto);
});
// Reiniciar variables:
nombre_prod_ctrl.clear();
descripcion_prod_ctrl.clear();
valor_prod_ctrl.clear();
ddlVlr_tipoProducto = tipos_producto
.elementAt(0)["ID_TIPO_PRODUCTO"]
.toString();
ddlVlr_marca = marcas.elementAt(0)["ID_MARCA"].toString();
Navigator.pop(context); // ypu can pop item too
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// you can skip Scaffold and just using column(AppBar+ Expanded(ListView) ) also do the job
appBar: AppBar(
backgroundColor: Color.fromRGBO(0, 120, 215, 1),
titleTextStyle: TextStyle(color: Colors.white, fontSize: 20),
title: Text("Nuevo producto"),
),
body: ListView(
children: [
DropdownButton(
hint: Text("Tipo de producto"),
icon: Icon(Icons.arrow_drop_down),
items: tipos_producto.map((map) {
return DropdownMenuItem<String>(
value: map["ID_TIPO_PRODUCTO"].toString(),
child: Text(map["NOMBRE_TIPO_PRODUCTO"]),
);
}).toList(),
value: ddlVlr_tipoProducto,
onChanged: (String? value) {
setState(() {
ddlVlr_tipoProducto = value!;
});
},
),
DropdownButton(
hint: Text("Marca"),
icon: Icon(Icons.arrow_drop_down),
value: ddlVlr_marca,
onChanged: (String? value) {
setState(() {
ddlVlr_marca = value!;
});
},
items: marcas.map((map) {
return DropdownMenuItem<String>(
value: map["ID_MARCA"].toString(),
child: Text(map["NOMBRE_MARCA"]),
);
}).toList(),
),
/// ... put those textFiled here
ElevatedButton.icon(
label: Text("Guardar"),
icon: Icon(Icons.save),
onPressed: _agregarProducto,
),
],
),
);
}
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。