listview 不随 setState 更新
listview does not update with setState
我有一张带有 TexField 的卡片和一个指向 select 图片的按钮。
在图像被 selected 之后,图标必须被图像替换,并且在 selection 对话框中必须显示项目删除。
它在固定的第一张卡片上完美运行,但在 Listview 中动态插入的卡片上它不起作用,因为它不使用 setState 更新。
如何更新列表视图?
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
void main() => runApp(MaterialApp(
home: NewPollPage(),
));
class NewPollPage extends StatefulWidget {
@override
_NewPollPageState createState() => _NewPollPageState();
}
class _NewPollPageState extends State<NewPollPage> {
Map photoMap = {};
var cards = <Card>[];
var newAlt = <TextEditingController>[];
var alternativaController = TextEditingController();
var alternativa1Controller = TextEditingController();
String indice = UniqueKey().toString();
File cropped;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: Text('Cards'),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
Card(
elevation: 0,
color: Color(0xFAFAFA),
child: Row(
children: [
!photoMap.containsKey('foto_n1')
? IconButton(
padding: EdgeInsets.only(bottom: 18.0),
icon: Icon(Icons.camera_alt),
color: Colors.black,
onPressed: () {
setState(() {
selectImage(context, 'n1', false);
});
},
)
: GestureDetector(
child: Container(
padding: EdgeInsets.only(bottom: 20.0),
width: 50,
height: 46,
child: Image.file(photoMap['foto_n1']),
),
onTap: () {
setState(() {
selectImage(context, 'n1', true);
});
},
),
Expanded(
child: TextField(
controller: alternativa1Controller,
maxLength: 60,
decoration: InputDecoration(
hintText: 'Digite a alternativa',
contentPadding: EdgeInsets.only(left: 12, top: 22),
fillColor: Color(0xFFE3E4E8),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25.0),
),
),
),
),
],
),
),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: cards.length,
itemBuilder: (context, index) {
indice = UniqueKey().toString();
return cards[index];
},
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Colors.black,
onPressed: () {
setState(() => cards.add(createCard(indice)));
},
),
);
}
Card createCard(indice) {
var alternativaController = TextEditingController();
newAlt.add(alternativaController);
return Card(
elevation: 0,
color: Color(0xFAFAFA),
child: Row(
children: [
!photoMap.containsKey('foto_$indice')
? IconButton(
padding: EdgeInsets.only(bottom: 18.0),
icon: Icon(Icons.camera_alt),
color: Colors.black,
onPressed: () {
setState(() {
selectImage(context, '$indice', false);
});
},
)
: GestureDetector(
child: Container(
padding: EdgeInsets.only(bottom: 20.0),
width: 50,
height: 46,
child: Image.file(photoMap['foto_$indice']),
),
onTap: () {
setState(() {
selectImage(context, '$indice', true);
});
},
),
Expanded(
child: TextField(
maxLength: 60,
decoration: InputDecoration(
hintText: 'Digite a alternativa',
contentPadding: EdgeInsets.only(left: 12, top: 22),
fillColor: Color(0xFFE3E4E8),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25.0),
),
),
),
),
],
),
);
}
selectImage(parentContext, indice, visivel) {
return showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: Text('Selecione:'),
children: [
if (photoMap.containsKey('foto_$indice'))
Image.file(photoMap['foto_$indice']),
SimpleDialogOption(
child: Row(
children: [
Icon(Icons.camera_alt),
Text(' Tirar foto com a câmera'),
],
),
onPressed: () => getImage(ImageSource.camera, indice)),
SimpleDialogOption(
child: Row(
children: [
Icon(Icons.photo_library),
Text(' Foto da galeria'),
],
),
onPressed: () => getImage(ImageSource.gallery, indice)),
Visibility(
visible: visivel,
child: SimpleDialogOption(
child: Row(
children: [
Icon(Icons.delete),
Text(' Apagar'),
],
),
onPressed: () => null //apagarFoto(mapa),
),
),
SimpleDialogOption(
child: Row(
children: [
Icon(Icons.cancel),
Text(' Cancelar'),
],
),
onPressed: () => Navigator.pop(context),
)
],
);
});
}
Future getImage(ImageSource source, indice) async {
Navigator.pop(context);
PickedFile imagePicker = await ImagePicker().getImage(source: source);
if (imagePicker != null) {
cropped = await ImageCropper.cropImage(
sourcePath: imagePicker.path,
aspectRatio: CropAspectRatio(ratioX: 4, ratioY: 3),
compressQuality: 70,
maxWidth: 700,
maxHeight: 700,
compressFormat: ImageCompressFormat.jpg,
androidUiSettings: AndroidUiSettings(
toolbarColor: Colors.black,
toolbarTitle: "Foto",
statusBarColor: Colors.black,
backgroundColor: Colors.black,
toolbarWidgetColor: Colors.white,
dimmedLayerColor: Colors.black,
),
);
if (cropped != null) {
setState(() {
photoMap['foto_$indice'] = cropped;
});
}
}
}
}
您不应该在稍后更新的列表中传递小部件。相反,你应该有一个函数,它将 return 你的小部件基于列表的索引。
而不是
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: cards.length,
itemBuilder: (context, index) {
indice = UniqueKey().toString();
return cards[index];
},
)
你应该做
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: photoMap.length,
itemBuilder: (context, index) {
return _cardWidget(index);
},
)
在_cardWidget函数中
Card _cardWidget(int index) {
return Card(
elevation: 0,
color: Color(0xFAFAFA),
child: Row(
children: [
photoMap['foto_$index'] == ''
? IconButton(
padding: EdgeInsets.only(bottom: 18.0),
icon: Icon(Icons.camera_alt),
color: Colors.black,
onPressed: () {
setState(() {
selectImage(context, '$index', false);
});
},
)
: GestureDetector(
child: Container(
padding: EdgeInsets.only(bottom: 20.0),
width: 50,
height: 46,
child: Image.file(photoMap['foto_$index']),
),
onTap: () {
setState(() {
selectImage(context, '$index', true);
});
},
),
Expanded(
child: TextField(
maxLength: 60,
decoration: InputDecoration(
hintText: 'Digite a alternativa',
contentPadding: EdgeInsets.only(left: 12, top: 22),
fillColor: Color(0xFFE3E4E8),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25.0),
),
),
),
),
],
),
);
}
在 photoMap 中添加空字符串的 key 以比较是否包含文件
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Colors.black,
onPressed: () {
setState(() => photoMap["foto_${photoMap.length}"] = '');
},
),
更改 selectImage 中的比较逻辑
if (photoMap['foto_$indice'] != '')Image.file(photoMap['foto_$indice']),
我有一张带有 TexField 的卡片和一个指向 select 图片的按钮。 在图像被 selected 之后,图标必须被图像替换,并且在 selection 对话框中必须显示项目删除。 它在固定的第一张卡片上完美运行,但在 Listview 中动态插入的卡片上它不起作用,因为它不使用 setState 更新。 如何更新列表视图?
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
void main() => runApp(MaterialApp(
home: NewPollPage(),
));
class NewPollPage extends StatefulWidget {
@override
_NewPollPageState createState() => _NewPollPageState();
}
class _NewPollPageState extends State<NewPollPage> {
Map photoMap = {};
var cards = <Card>[];
var newAlt = <TextEditingController>[];
var alternativaController = TextEditingController();
var alternativa1Controller = TextEditingController();
String indice = UniqueKey().toString();
File cropped;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: Text('Cards'),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
Card(
elevation: 0,
color: Color(0xFAFAFA),
child: Row(
children: [
!photoMap.containsKey('foto_n1')
? IconButton(
padding: EdgeInsets.only(bottom: 18.0),
icon: Icon(Icons.camera_alt),
color: Colors.black,
onPressed: () {
setState(() {
selectImage(context, 'n1', false);
});
},
)
: GestureDetector(
child: Container(
padding: EdgeInsets.only(bottom: 20.0),
width: 50,
height: 46,
child: Image.file(photoMap['foto_n1']),
),
onTap: () {
setState(() {
selectImage(context, 'n1', true);
});
},
),
Expanded(
child: TextField(
controller: alternativa1Controller,
maxLength: 60,
decoration: InputDecoration(
hintText: 'Digite a alternativa',
contentPadding: EdgeInsets.only(left: 12, top: 22),
fillColor: Color(0xFFE3E4E8),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25.0),
),
),
),
),
],
),
),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: cards.length,
itemBuilder: (context, index) {
indice = UniqueKey().toString();
return cards[index];
},
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Colors.black,
onPressed: () {
setState(() => cards.add(createCard(indice)));
},
),
);
}
Card createCard(indice) {
var alternativaController = TextEditingController();
newAlt.add(alternativaController);
return Card(
elevation: 0,
color: Color(0xFAFAFA),
child: Row(
children: [
!photoMap.containsKey('foto_$indice')
? IconButton(
padding: EdgeInsets.only(bottom: 18.0),
icon: Icon(Icons.camera_alt),
color: Colors.black,
onPressed: () {
setState(() {
selectImage(context, '$indice', false);
});
},
)
: GestureDetector(
child: Container(
padding: EdgeInsets.only(bottom: 20.0),
width: 50,
height: 46,
child: Image.file(photoMap['foto_$indice']),
),
onTap: () {
setState(() {
selectImage(context, '$indice', true);
});
},
),
Expanded(
child: TextField(
maxLength: 60,
decoration: InputDecoration(
hintText: 'Digite a alternativa',
contentPadding: EdgeInsets.only(left: 12, top: 22),
fillColor: Color(0xFFE3E4E8),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25.0),
),
),
),
),
],
),
);
}
selectImage(parentContext, indice, visivel) {
return showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: Text('Selecione:'),
children: [
if (photoMap.containsKey('foto_$indice'))
Image.file(photoMap['foto_$indice']),
SimpleDialogOption(
child: Row(
children: [
Icon(Icons.camera_alt),
Text(' Tirar foto com a câmera'),
],
),
onPressed: () => getImage(ImageSource.camera, indice)),
SimpleDialogOption(
child: Row(
children: [
Icon(Icons.photo_library),
Text(' Foto da galeria'),
],
),
onPressed: () => getImage(ImageSource.gallery, indice)),
Visibility(
visible: visivel,
child: SimpleDialogOption(
child: Row(
children: [
Icon(Icons.delete),
Text(' Apagar'),
],
),
onPressed: () => null //apagarFoto(mapa),
),
),
SimpleDialogOption(
child: Row(
children: [
Icon(Icons.cancel),
Text(' Cancelar'),
],
),
onPressed: () => Navigator.pop(context),
)
],
);
});
}
Future getImage(ImageSource source, indice) async {
Navigator.pop(context);
PickedFile imagePicker = await ImagePicker().getImage(source: source);
if (imagePicker != null) {
cropped = await ImageCropper.cropImage(
sourcePath: imagePicker.path,
aspectRatio: CropAspectRatio(ratioX: 4, ratioY: 3),
compressQuality: 70,
maxWidth: 700,
maxHeight: 700,
compressFormat: ImageCompressFormat.jpg,
androidUiSettings: AndroidUiSettings(
toolbarColor: Colors.black,
toolbarTitle: "Foto",
statusBarColor: Colors.black,
backgroundColor: Colors.black,
toolbarWidgetColor: Colors.white,
dimmedLayerColor: Colors.black,
),
);
if (cropped != null) {
setState(() {
photoMap['foto_$indice'] = cropped;
});
}
}
}
}
您不应该在稍后更新的列表中传递小部件。相反,你应该有一个函数,它将 return 你的小部件基于列表的索引。
而不是
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: cards.length,
itemBuilder: (context, index) {
indice = UniqueKey().toString();
return cards[index];
},
)
你应该做
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: photoMap.length,
itemBuilder: (context, index) {
return _cardWidget(index);
},
)
在_cardWidget函数中
Card _cardWidget(int index) {
return Card(
elevation: 0,
color: Color(0xFAFAFA),
child: Row(
children: [
photoMap['foto_$index'] == ''
? IconButton(
padding: EdgeInsets.only(bottom: 18.0),
icon: Icon(Icons.camera_alt),
color: Colors.black,
onPressed: () {
setState(() {
selectImage(context, '$index', false);
});
},
)
: GestureDetector(
child: Container(
padding: EdgeInsets.only(bottom: 20.0),
width: 50,
height: 46,
child: Image.file(photoMap['foto_$index']),
),
onTap: () {
setState(() {
selectImage(context, '$index', true);
});
},
),
Expanded(
child: TextField(
maxLength: 60,
decoration: InputDecoration(
hintText: 'Digite a alternativa',
contentPadding: EdgeInsets.only(left: 12, top: 22),
fillColor: Color(0xFFE3E4E8),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25.0),
),
),
),
),
],
),
);
}
在 photoMap 中添加空字符串的 key 以比较是否包含文件
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Colors.black,
onPressed: () {
setState(() => photoMap["foto_${photoMap.length}"] = '');
},
),
更改 selectImage 中的比较逻辑
if (photoMap['foto_$indice'] != '')Image.file(photoMap['foto_$indice']),