如果我删除或添加项目,我想更新ListView。现在,我只想删除项目,然后立即查看项目的删除。
我的应用程序更复杂,所以我写了一个小示例项目来展示我的问题。
TestItem
类包含一些数据条目:
class TestItem {
static int id = 1;
bool isFinished = false;
String text;
TestItem() {
text = "Item ${id++}";
}
}
ItemInfoViewWidget
是TestItem
的UI表示形式,如果完成则将其删除(当Checkbox更改为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?
编辑:这不是重复的,因为
this.items = List.from(this.items);
,但我的应用的行为与上面已经描述的相同。List.from
来破坏我的引用链,因为我的体系结构具有一个由多个类引用的列表。如果我中断了链接,我必须自己更新所有参考。我的架构有问题吗? 答案 0 :(得分:4)
我不想为更新用户界面而创建列表的新实例。
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,
);
}
}