如何在不重建整个列表的情况下更新列表项

时间:2020-10-25 17:38:26

标签: list flutter listview dart key

有人问我这个问题,我想让答案变得容易

如何在不重建整个列表的情况下更新列表项?

一个典型的用例(我将在下面的答案中重现)

可能是一个ListView,可能从List接收WidgetAPI

1 个答案:

答案 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);
  }
}

相关问题