Flutter Provider-重建列表项而不是列表视图

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter Provider-重建列表项而不是列表视图相关的知识,希望对你有一定的参考价值。

我正在使用Provider程序包来管理我的应用程序的业务逻辑,但是遇到了一个问题,其中整个ListView正在重建,而不是单个ListTile。这是UI,可让您更好地理解:

enter image description here

[当前,如果我滚动到列表的底部,点击最后一项的复选框,则该复选框的切换没有动画,并且滚动条跳到了屏幕的顶部,因为整个小部件都已重建。 我如何使用提供程序,以便仅重建单个ListTile,而不重建列表中的每个项目?以下是一些相关代码:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Checklist',
        theme: ThemeData(
          brightness: Brightness.light,
          primaryColor: Colors.indigo[500],
          accentColor: Colors.amber[500],
        ),
        home: ChecklistHomeScreen(),
      ),
      providers: [
        ChangeNotifierProvider(
          create: (ctx) => ChecklistsProvider(),
        ),
      ],
    );
  }
}
class ChecklistHomeScreen extends StatefulWidget {
  @override
  _ChecklistHomeScreenState createState() => _ChecklistHomeScreenState();
}

class _ChecklistHomeScreenState extends State<ChecklistHomeScreen> {
  void createList(BuildContext context, String listName) {
    if (listName.isNotEmpty) {
      Provider.of<ChecklistsProvider>(context).addChecklist(listName);
    }
  }

  @override
  Widget build(BuildContext context) {
    final _checklists = Provider.of<ChecklistsProvider>(context).checklists;
    final _scaffoldKey = GlobalKey<ScaffoldState>();
    ScrollController _scrollController =
        PrimaryScrollController.of(context) ?? ScrollController();

    return Scaffold(
      key: _scaffoldKey,
      body: CustomScrollView(
        controller: _scrollController,
        slivers: <Widget>[
          SliverAppBar(
            floating: true,
            pinned: false,
            title: Text('Your Lists'),
            centerTitle: true,
            actions: <Widget>[
              PopupMenuButton(
                itemBuilder: (ctx) => null,
              ),
            ],
          ),
          ReorderableSliverList(
            delegate: ReorderableSliverChildBuilderDelegate(
              (ctx, i) => _buildListItem(_checklists[i], i),
              childCount: _checklists.length,
            ),
            onReorder: (int oldIndex, int newIndex) {
              setState(() {
                final checklist = _checklists.removeAt(oldIndex);
                _checklists.insert(newIndex, checklist);
              });
            },
          ),
        ],
      ),
      drawer: Drawer(
        child: null,
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }

  Widget _buildListItem(Checklist list, int listIndex) {
    return Dismissible(
      key: ObjectKey(list.id),
      direction: DismissDirection.endToStart,
      background: Card(
        elevation: 0,
        child: Container(
          alignment: AlignmentDirectional.centerEnd,
          color: Theme.of(context).accentColor,
          child: Padding(
            padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
            child: Icon(
              Icons.delete,
              color: Colors.white,
            ),
          ),
        ),
      ),
      child: Card(
        child: ListTile(
          onTap: null,
          title: Text(list.name),
          leading: Checkbox(
            value: list.completed,
            onChanged: (value) {
              Provider.of<ChecklistsProvider>(context)
                  .toggleCompletedStatus(list.id, list.completed);
            },
          ),
          trailing: IconButton(
            icon: Icon(Icons.more_vert),
            onPressed: null,
          ),
        ),
      ),
      onDismissed: (direction) {
        _onDeleteList(list, listIndex);
      },
    );
  }

  void _onDeleteList(Checklist list, int listIndex) {
    Scaffold.of(context).removeCurrentSnackBar();
    Scaffold.of(context).showSnackBar(
      SnackBar(
        action: SnackBarAction(
          label: 'UNDO',
          onPressed: () {
            Provider.of<ChecklistsProvider>(context)
                .undoDeleteChecklist(list, listIndex);
          },
        ),
        content: Text(
          'List deleted',
          style: TextStyle(color: Theme.of(context).accentColor),
        ),
      ),
    );
  }
}
class ChecklistsProvider with ChangeNotifier {
  final ChecklistRepository _repository = ChecklistRepository(); //singleton

  UnmodifiableListView<Checklist> get checklists => UnmodifiableListView(_repository.getChecklists());

  void addChecklist(String name) {
    _repository.addChecklist(name);
    notifyListeners();
  }

  void deleteChecklist(int id) {
    _repository.deleteChecklist(id);
    notifyListeners();
  }

  void toggleCompletedStatus(int id, bool completed) {
    final list = checklists.firstWhere((c) => c.id == id);
    if(list != null) {
      list.completed = completed;
      _repository.updateChecklist(list);
      notifyListeners();
    }
  }
}

我应该说我理解为什么这是当前行为,只是不确定是否要重建仅我要更新的列表项而不是整个屏幕的正确方法。我也读过Consumer,但不确定如何将其适合我的实现。

答案

消费者本质上将允许您使用对更改通知者所做的任何更改。最佳实践是将Consumer尽可能深入地嵌入到您的构建方法中。这样,只有包装的窗口小部件才能重新构建。该文档对此进行了很好的解释:https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple

尝试将您的CheckBox小部件包装在Consumer小部件中:仅应重建该复选框。

Consumer<ChecklistsProvider>(
  builder: (context, provider, _) {
    return Checkbox(
      value: list.completed,
      onChanged: (value) {
        provider.toggleCompletedStatus(list.id, list.completed);
      },
    );
  },
),

如果您希望重新构建ListTile和CheckBox,只需将ListTile包装在Consumer中

以上是关于Flutter Provider-重建列表项而不是列表视图的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Provider 重建不必要的小部件

在 Flutter 中使用 Provider 更新和动画化 AnimatedList

Flutter Provider 不会重建小部件

跨多个屏幕使用的 Flutter Stateful Widget 正在重建

使用上下文和观察更改模型时,Flutter Provider 不会触发重建

由于当前屏幕上的更新,防止 Flutter Provider 在前一屏幕中重建小部件