我有一个 ListView.builder,它使用一个简单提供者的消费者。 每个项目都需要异步加载一些东西,所以我正在使用 FutureBuilder。
当我向列表中添加一个项目时,整个列表被重建。这是有道理的,因为它被包裹在消费者中,但不幸的是,这意味着现有的项目需要重新运行未来,尽管这些项目实际上并没有改变。
class ListWithButton extends StatelessWidget {
const ListWithButton({super.key});
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(
builder: (_, provider, __) => Column(
children: [
ElevatedButton(
onPressed: () {
provider.addItem();
},
child: const Text("Add Item"),
),
Expanded(
child: ListView.builder(
itemCount: provider.items.length,
itemBuilder: (_, index) => FutureBuilder<Color>(
future: provider.loadSomething(index),
builder: (_, snapshot) => ListTile(
leading: Container(
width: 30,
height: 30,
color: snapshot.connectionState == ConnectionState.done
? snapshot.data!
: Colors.red,
),
title: Text(index.toString()),
),
),
),
),
],
),
);
}
}
class MyProvider extends ChangeNotifier {
final List<int> _items = [];
List<int> get items => _items;
void addItem() {
_items.add(_items.length);
notifyListeners();
}
Future<Color> loadSomething(int i) async {
await Future.delayed(Duration(seconds: 1));
return Colors.green;
}
}
有没有办法只重建 ListView 中需要构建的项目(即新项目)?
您不一定能阻止列表在添加新子项时重建其子小部件,但您可以通过在构建方法之外初始化和存储它们来防止 Futures 被重新初始化,这在 FutureBuilder 的文档中推荐:
未来必须早先获得,例如在 State.initState、State.didUpdateWidget 或 State.didChangeDependencies 期间。在构造 FutureBuilder 时,不得在 State.build 或 StatelessWidget.build 方法调用期间创建它。如果 Future 与 FutureBuilder 同时创建,那么每次 FutureBuilder 的父级重建时,都会重新启动异步任务。
为此,您只需更改 MyProvider,如下所示:
class MyProvider extends ChangeNotifier {
final List<int> _items = [];
final Map<int, Future<Color>> _futureColors = {};
List<int> get items => _items;
void addItem() {
_items.add(_items.length);
notifyListeners();
}
Future<Color>? loadSomething(int i) {
if (!_futureColors.containsKey(i)) {
_futureColors[i] = Future.delayed(
const Duration(seconds: 1),
() => Colors.green,
);
}
return _futureColors[i];
}
}
在这里,我们只在第一次使用给定索引调用
loadSomething
时创建一个新的未来——我们将该未来存储在一个 Map 中,并且在以后调用 loadSomething
时,我们返回已经为该索引初始化的 Future。 (我使用 Map
而不是 List
的原因是无法保证该函数将接收有效的列表索引)。
还要注意
loadSomething
不再是async
函数——这很重要,因为我们希望未来同步返回到FutureBuilder
。如果我们异步返回 future,您可能会注意到,当您添加新元素时,有时 List 的元素仍然闪烁红色。