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

rej*_*y11 5 flutter

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

在此处输入图片说明

目前,如果我滚动到列表底部,点击最后一个项目的复选框,我看不到复选框切换的动画,滚动会跳到屏幕顶部,因为整个小部件已经重建。如何使用 Provider 以便仅重建单个 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(),
        ),
      ],
    );
  }
}
Run Code Online (Sandbox Code Playgroud)
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),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)
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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我应该说我明白为什么这是当前的行为,我只是不确定正确的方法来确保只重建我想要更新的列表项而不是整个屏幕。我也读过,Consumer但我不确定如何将它融入我的实现中。

mag*_*e05 8

消费者本质上允许您使用对更改通知程序所做的任何更改。最佳实践是将 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);
      },
    );
  },
),
Run Code Online (Sandbox Code Playgroud)

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