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

时间:2019-12-06 14:48:33

标签: flutter

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

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的内容,但不确定如何将其放入实现中。

2 个答案:

答案 0 :(得分:0)

我解决该问题的方法是创建另一个Provider类,然后该类仅由每个Checkbox的{​​{1}}小部件使用。

ListTile

这样,只有leading: Consumer<ChecklistTileProvider>( builder: (ctx, provider, _) => Checkbox( value: list.completed, onChanged: (value) { provider.toggleCompletedStatus(list.id, value); }, ), ), 会重建,而不是整个页面。我不确定这是否是推荐的方法,但它在解决我的问题方面做得很好:)

答案 1 :(得分:-1)

从本质上讲,消费者将允许您使用对更改通知者所做的任何更改。最佳实践是将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中