从列表中删除项目后更新UI
问题内容:
如果要删除或添加项目,我想更新ListView。现在,我只想删除项目,然后立即查看项目的删除。
我的应用程序更复杂,因此我写了一个小示例项目来展示我的问题。
本TestItem
类包含一些数据项:
class TestItem {
static int id = 1;
bool isFinished = false;
String text;
TestItem() {
text = "Item ${id++}";
}
}
该ItemInfoViewWidget
是对的UI表示TestItem
,如果它完成(当该复选框被更改为true)中删除的项目。
class ItemInfoViewWidget extends StatefulWidget {
TestItem item;
List<TestItem> items;
ItemInfoViewWidget(this.items, this.item);
@override
_ItemInfoViewWidgetState createState() =>
_ItemInfoViewWidgetState(this.items, this.item);
}
class _ItemInfoViewWidgetState extends State<ItemInfoViewWidget> {
TestItem item;
List<TestItem> items;
_ItemInfoViewWidgetState(this.items, this.item);
@override
Widget build(BuildContext context) {
return new Card(
child: new Column(
children: <Widget>[
new Text(this.item.text),
new Checkbox(
value: this.item.isFinished, onChanged: isFinishedChanged)
],
),
);
}
void isFinishedChanged(bool value) {
setState(() {
this.item.isFinished = value;
this.items.remove(this.item);
});
}
}
本ItemViewWidget
类构建ListView
。
class ItemViewWidget extends StatefulWidget {
List<TestItem> items;
ItemViewWidget(this.items);
@override
_ItemViewWidgetState createState() => _ItemViewWidgetState(this.items);
}
class _ItemViewWidgetState extends State<ItemViewWidget> {
List<TestItem> items;
_ItemViewWidgetState(this.items);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text('Test'),
),
body: ListView.builder(
itemCount: this.items.length,
itemBuilder: (BuildContext context, int index) {
return new ItemInfoViewWidget(this.items, this.items[index]);
}),
);
}
}
该MyApp
节目之一TestItem
和一个按钮,导航到该ItemViewWidget
页面。
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<TestItem> items = new List<TestItem>();
_MyHomePageState() {
for (int i = 0; i < 20; i++) {
this.items.add(new TestItem());
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
children: <Widget>[
ItemInfoViewWidget(this.items, this.items.first),
FlatButton(
child: new Text('Open Detailed View'),
onPressed: buttonClicked,
)
],
));
}
void buttonClicked() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ItemViewWidget(this.items)),
);
}
}
如果我切换第一项的复选框,则该复选框被标记为已完成(如预期),但并未从UI中删除-但是已从列表中删除了该复选框。
然后,我回到主页,可以看到在那里也检查了项目1。
因此,如果我ItemViewWidget
再次转到该页面,则可以看到已检查的项目不再存在。基于这些观察,我得出的结论是我的实现有效,但是我的UI没有更新。
如何更改代码以使UI可以立即更新?
编辑:这不是重复的,因为
- 我不想仅为更新UI而创建列表的新实例。
- 答案不起作用:我添加了,
this.items = List.from(this.items);
但是我的应用程序的行为与上面已经描述的相同。 - 我不想通过调用破坏参考链
List.from
,因为我的体系结构具有一个由多个类引用的列表。如果我中断了链接,我必须自己更新所有参考。我的架构有问题吗?
问题答案:
我不想仅为更新UI而创建列表的新实例。
Flutter使用不可变对象。不遵循此规则会违反反应框架。减少错误是自愿的。
事实是,这种不变性在这里特别是为了防止开发人员执行当前的工作:具有依赖于类之间共享对象的同一实例的程序;因为可能要修改多个类。
真正的问题在于,这是您的列表项从列表中删除删除元素。
事情是因为是您的项目在进行计算,因此永远不会通知父项列表已更改。因此,它不知道应该重新渲染。因此,外观没有任何变化。
要解决此问题,您应该将删除逻辑移至父级。并确保父级正确地进行相应的调用setState
。这将转化为将回调传递给您的列表项,删除后将调用该回调。
这是一个例子:
class MyList extends StatefulWidget {
@override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
List<String> list = List.generate(100, (i) => i.toString());
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return MyItem(list[index], onDelete: () => removeItem(index));
},
);
}
void removeItem(int index) {
setState(() {
list = List.from(list)
..removeAt(index);
});
}
}
class MyItem extends StatelessWidget {
final String title;
final VoidCallback onDelete;
MyItem(this.title, {this.onDelete});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(this.title),
onTap: this.onDelete,
);
}
}