我试图构建一个页面,用户可以在其中添加和删除列表中的元素。我遇到了一个问题。当我删除一个元素时,模型会正确更新,同时UI也会更新,但方式错误:仅移除列表的最后一个元素。
我用的是Flutter 1.5.4版。
我已经在列表中使用了更简单的元素,并且我试图用这个页面来构建一个新项目,以消除所有可能的问题,但它仍然不能正确工作。
我也试过用列代替列表,但结果总是一样。
main.dart:
import 'package:flutter/material.dart';
import './widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Hello'),
),
body: Center(
child: SectionWidget(),
),
),
);
}
}
widget.dart:
import 'package:flutter/material.dart';
class SectionWidget extends StatefulWidget {
_SectionWidgetState createState() => new _SectionWidgetState();
}
class _SectionWidgetState extends State<SectionWidget> {
List<String> _items = List<String>();
@override
Widget build(BuildContext context) {
List<Widget> children = [
ListTile(
title: Text(
"Strings",
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: IconButton(
icon: Icon(Icons.add_circle),
onPressed: () => setState(() {
_items.add('item ${_items.length}');
print("Adding "+ _items.last);
}),
),
),
];
children.addAll(_buildForms());
return ListView(
children: children,
);
}
List<Widget> _buildForms() {
List<Widget> forms = new List<Widget>();
print("Strings:" + _items.toString());
for (String item in _items) {
forms.add(
ListTile(
leading: Icon(Icons.person),
title: TextFormField(
initialValue: item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => setState(() {
print("Removing $item");
_items.remove(item);
}),
),
),
);
}
return forms;
}
}
如果我向列表中添加4项,然后删除“item 1”,则控制台上的输出如下:
I/flutter ( 4192): Strings:[]
I/flutter ( 4192): Adding item 0
I/flutter ( 4192): Strings:[item 0]
I/flutter ( 4192): Adding item 1
I/flutter ( 4192): Strings:[item 0, item 1]
I/flutter ( 4192): Adding item 2
I/flutter ( 4192): Strings:[item 0, item 1, item 2]
I/flutter ( 4192): Adding item 3
I/flutter ( 4192): Strings:[item 0, item 1, item 2, item 3]
I/flutter ( 4192): Removing item 1
I/flutter ( 4192): Strings:[item 0, item 2, item 3]
这是正确的,因为“item 1”已从列表中删除,但如果我查看UI,则会看到以下内容:
列表中的元素数量正确,型号正确,但显示的元素错误。
我该如何解决这个问题?是我做错了什么还是Flutter的bug?
8条答案
按热度按时间qcbq4gxm1#
您可以做的是将
Key
传递给ListView
,Key
将包含列表中元素的长度。为什么有效
它会导致整个
ListView
从头开始重建,但只有当列表的长度改变时才会发生。重要的是,它不会在其他情况下发生(比如在TextField中键入),因为这样的操作会导致您无法关注每一个字母类型。希望能有所帮助;)
xyhw6mcr2#
这不是一个解决方案,而是一个解释,参见Marcin Szałek对解决方案的评论。
ListView
没有按预期重新呈现的原因是TextFormField
是一个有状态的小部件。当您重新构建小部件时,您需要注意有3棵树,它们是Widget、Element和RenderObject树。RenderObject是可视化表示,直接基于Element。而Element直接基于Widget,而Widget只是配置,即您在代码中键入的内容。首次示例化小部件时,会创建一个对应的元素。但是,当您调用
setState()
更改_items列表时,会重新构建小部件,但不会破坏和重新创建元素。相反,为了最大化效率,Flutter会首先比较初始ListTile
小部件和重新构建的ListTile
小部件之间的任何差异。在这种情况下,最初有4个瓦片,之后有3个瓦片。代替重新创建新瓦片,更新第二和第三个切片的元素,使其具有rebuildListTile
小部件的属性(并且第一个图块没有发生变化,因为前后的小部件/配置是相同的).第4个元素从元素树中弹出。官方Flutter团队的这款X1 E0 F1 X在解释这三棵树方面做得很好。使用Dart的Devtools,您可以发现第三个元素的key/id没有任何变化,当您检查第三个
ListTile
时,在key之前和之后都是 *#3ded *。姓名首字母
第一节第一节第一节第一节第一次
删除后
第一节第二节第一节第三节第一节
要从树中删除元素然后再放回去,请在
ListView
上附加一个key,如Marcin Szałek所述。对于有状态的小部件,当对应的新小部件具有相同的小部件类型和key时,元素将被更新。如果没有显式指定key,元素将只检查它是否与对应的小部件具有相同的类型。由于所有的小部件都是运行时类型ListTile
,因此不会有任何元素的更新,因此您看到的是item 0和item 1,而不是item 0和item 2。我建议您阅读更多关于key here的内容。使用Dart的Devtools,可以发现第三个
ListTile
的键发生了变化:最初,当它是item 1时,键是 #5addd,然后,当item 1被删除时,键变为 #26044。姓名首字母
一个四个一个一个一个五个一个
删除后
第一节第六节第一节第一节第七节第一节
qxsslcnc3#
删除而不更新已删除元素的原因是第二个小部件是有状态小部件,在某些情况下需要强制呈现
使用以下代码通过覆盖didUpdateWidget方法来获取reload视图
jtw3ybtb4#
我尝试Marcin Szałek解决方案,但我使用BLoC来重建小部件,因此他的解决方案导致了如下问题:
1-将第一个元素(TextField)添加到列表中并在其中键入“a
2-将第二个元素(TextField)添加到列表中(第一个TextField将变为空)
实际上我不知道如果我们不使用BLoC是否会发生这种情况,但如果你使用BLoC,试试这个解决方案,它对我来说就像一个魅力:
1-创建
UniqueKey
的列表2-当你点击添加图标添加一个元素到
ListView
时,确保添加UniqueKey
对象到listKeys
3-将每个
listKeys
列表成员附加到ListView
项4-从
ListView
中删除元素“TextField”时,必须将其从listKyes
中删除以cs7cruho5#
我认为,你需要为每个文本域创建控制器或者使用hintText
j13ufse26#
你的小部件状态只包含String列表,当你删除其中一个并重建小部件时,Flutter知道你的
ListView
少了一个子小部件,所以它删除最后一个并重建其他的。这里的问题是,您使用
initialValue
设置TextFormFields
上的文本-initialValue
的全部意义在于,即使您更改字段上的文本,它也不会更改它-TextFormField
只能有1个初始值,这就是您最初设置的值。如果你想改变文本,你需要使用一个
TextEditingController
。在上面的例子中,简单地用controller: TextEditingController(text: item)
替换initialValue: item
就可以解决这个问题。在一个真实的的应用程序中,你可能想把控制器存储在小部件的状态中,这样用户输入的值就可以在重建过程中保持不变。xsuvu9jc7#
据我所知,
setState
函数只更新窗口小部件数据,不重新创建窗口小部件。在您的代码中,您使用initialValue设置TextField
的默认文本。此参数仅在创建TextField
时使用。因此,当您删除一个项目时,应用程序将根据_item
的长度更新窗口小部件(在本例中,保留3个第一个,删除最后一个)。此时initialValue
没有意义,因为TextField
不是创建的,而是重用的。要解决此问题,可以使用TextEditingController而不是
initialVal
来设置文本首先,使用
_items
创建TextEditingController
Map列表然后,当添加一个项目也创建一个新的控制器
最后,将控制器添加到
TextField
啊,不要忘记删除项目之前删除控制器
希望有帮助。
yptwkmov8#
为TextFormField元素使用键或控制器字段。