有人问我这个问题,我想让答案变得容易
如何在不重建整个列表的情况下更新列表项?
一个典型的用例(我将在下面的答案中重现)
可能是一个ListView
,可能从List
接收Widget
个API
答案 0 :(得分:0)
在Key
中的Widget
中提供List
可以防止这种情况
被从小部件树中删除,因此被不必要地重建
您可以尝试自己运行此app in dartpad
在终端中记录日志;
代码发布在下面
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
FakeApi _api;
@override
void initState() {
_api = FakeApi(_navigatorKey);
super.initState();
}
@override
void dispose() {
_api?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => MaterialApp(
navigatorKey: _navigatorKey,
home: MyInheritedWidget(
api: _api,
child: const MyHomePage(),
),
);
}
class MyInheritedWidget extends InheritedWidget {
const MyInheritedWidget({
@required Widget child,
@required this.api,
}) : super(
key: const Key('MyInheritedWidget'),
child: child,
);
final FakeApi api;
static MyInheritedWidget of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
@override
bool updateShouldNotify(MyInheritedWidget old) => false;
}
class MyHomePage extends StatelessWidget {
const MyHomePage() : super(key: const Key('MyHomePage'));
@override
Widget build(BuildContext context) => Builder(
builder: (context) => Scaffold(
backgroundColor: Colors.blueGrey,
body: StreamBuilder<List<ItemWidget>>(
stream: MyInheritedWidget.of(context).api.stream,
initialData: [],
builder: (context, list) => list.hasError
? const Center(child: Icon(Icons.error))
: !list.hasData
? const Center(child: CircularProgressIndicator())
: list.data.isEmpty
? const Center(
child: Text(
'the list is empty',
textScaleFactor: 1.5,
))
: ListView.builder(
itemCount: list.data.length,
itemBuilder: (context, index) => list.data[index],
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.white,
child: const Icon(Icons.add, color: Colors.blueGrey),
onPressed: MyInheritedWidget.of(context).api.add,
),
),
);
}
class ItemWidget extends StatelessWidget {
ItemWidget(this.text) : super(key: UniqueKey());
final String text;
@override
Widget build(BuildContext context) {
print('Item $text is building');
return Center(
child: Container(
padding: const EdgeInsets.only(bottom: 20),
width: MediaQuery.of(context).size.width * .5,
child: Card(
elevation: 10,
child: ListTile(
leading: GestureDetector(
child: const Icon(Icons.edit),
onTap: () => MyInheritedWidget.of(context).api.edit(key),
),
trailing: GestureDetector(
child: const Icon(Icons.delete),
onTap: () => MyInheritedWidget.of(context).api.delete(key),
),
title: Text(text),
),
),
),
);
}
}
class ItemDialog extends StatefulWidget {
const ItemDialog({this.text});
final String text;
@override
_ItemDialogState createState() => _ItemDialogState();
}
class _ItemDialogState extends State<ItemDialog> {
TextEditingController _controller;
@override
void initState() {
_controller = TextEditingController()..text = widget.text;
super.initState();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => AlertDialog(
content: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * .3,
child: Center(
child: TextField(
autofocus: true,
controller: _controller,
),
),
),
],
),
actions: <Widget>[
IconButton(
onPressed: () => Navigator.pop(context, _controller.text ?? ''),
icon: const Icon(Icons.save),
),
],
);
}
class FakeApi {
FakeApi(this.navigatorKey);
final GlobalKey<NavigatorState> navigatorKey;
final _list = <ItemWidget>[];
StreamController<List<ItemWidget>> _controller;
StreamController<List<ItemWidget>> get _c =>
_controller ??= StreamController<List<ItemWidget>>.broadcast();
Stream<List<ItemWidget>> get stream => _c.stream;
void dispose() => _controller?.close();
void delete(Key key) {
_list.removeWhere((ItemWidget item) => item.key == key);
_c.sink.add(_list);
}
void edit(Key key) async {
final _item = _list.firstWhere((ItemWidget item) => item.key == key);
final _index = _list.lastIndexOf(_item);
final _text = await showDialog<String>(
context: navigatorKey.currentState.overlay.context,
builder: (context) => ItemDialog(
text: _item.text,
),
);
_list.removeAt(_index);
_list.insert(_index, ItemWidget(_text));
_c.sink.add(_list);
}
void add() async {
final _text = await showDialog<String>(
context: navigatorKey.currentState.overlay.context,
builder: (context) => ItemDialog(),
);
_list.add(ItemWidget(_text));
_c.sink.add(_list);
}
}