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

Posted

技术标签:

【中文标题】跨多个屏幕使用的 Flutter Stateful Widget 正在重建【英文标题】:Flutter Stateful Widget used across Multiple Screens getting Rebuilt 【发布时间】:2020-07-02 06:09:32 【问题描述】:

我创建了下面的 Multiselect Chip 小部件,它使用 Provider 并监听列表的变化

小部件创建一个选择筹码列表,允许选择多项选择筹码

class MultiSelectChip extends StatefulWidget 
  final Function(List<String>) onSelectionChanged;

  MultiSelectChip(this.onSelectionChanged);

  @override
  _MultiSelectChipState createState() => _MultiSelectChipState();


class _MultiSelectChipState extends State<MultiSelectChip> 
  List<String> selected = List();
  List<Clinic> clinicList = List();

  @override
  void didChangeDependencies() 
    final list = Provider.of<ClinicProvider>(context).clinics;

    final clinic = Clinic(
        id: null,
        name: "All Clinics",
        city: null,
        suburb: null,
        postcode: null,
        prate: null,
        udarate: null,
        goal: null,
        uid: null);

    clinicList.add(clinic);
    selected.add(clinicList[0].name);
    list.forEach((clinic) => clinicList.add(clinic));
    super.didChangeDependencies();
  

  _buildList() 
    List<Widget> choices = List();

    clinicList.forEach((item) 
      choices.add(Padding(
        padding: const EdgeInsets.only(left: 5.0, right: 5.0),
        child: ChoiceChip(
          key: Key("$item.name"),
          shape: selected.contains(item.name)
              ? RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(0),
                  ),
                )
              : RoundedRectangleBorder(
                  side: BorderSide(
                      color: Color.fromRGBO(46, 54, 143, 1), width: 1.0),
                  borderRadius: BorderRadius.circular(0.0),
                ),
          label: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(item.name),
          ),
          onSelected: (value) 
            setState(() 
              selected.contains(item.name)
                  ? selected.remove(item.name)
                  : selected.add(item.name);
              widget.onSelectionChanged(selected);
            );
          ,
          selected: selected.contains(item.name),
          selectedColor: Color.fromRGBO(46, 54, 143, 1),
          labelStyle:
              selected.contains(item.name) ? kChipActive : kChipInActive,
          backgroundColor: Colors.transparent,
        ),
      ));
    );

    return choices;
  

  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: const EdgeInsets.only(left: 8.0, top: 5.0, bottom: 5.0),
      child: SizedBox(
        height: 50,
        width: double.infinity,
        child: ListView(
          scrollDirection: Axis.horizontal,
          children: _buildList(),
        ),
      ),
    );
  

当我点击此日志屏幕并转到 NewLog 屏幕并弹回日志屏幕时

class LogScreen extends StatefulWidget 
  static const String id = 'logscreen';
  @override
  _LogScreenState createState() => _LogScreenState();


class _LogScreenState extends State<LogScreen> 
  MonthSelector selectedMonth;
  List<String> selectedItems = List();

  static DateTime now = DateTime.now();
  static DateTime end = DateTime(now.year, now.month + 1, 0);
  static DateTime start = DateTime(now.year, now.month, 1);

  MonthSelector currentMonth = MonthSelector(
      monthName: DateFormat("MMMM").format(now),
      monthStart: start,
      monthEnd: end);

  void refreshData(MonthSelector selector) async 
    await Provider.of<LogProvider>(context, listen: false)
        .getLogs(selector.monthStart, selector.monthEnd);
    await Provider.of<LogProvider>(context, listen: false)
        .loadTreatments(selector.monthStart, selector.monthEnd);
  

  @override
  Widget build(BuildContext context) 
    final List<LogSummary> list = Provider.of<LogProvider>(context).summary;
    final List<FlSpot> chartData = Provider.of<LogProvider>(context).spots;

    return Container(
      color: Color.fromRGBO(246, 246, 246, 1),
      child: Column(
        children: <Widget>[
          Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Expanded(
                  flex: 1,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      SizedBox(
                        height: 15,
                      ),
                      RawMaterialButton(
                        onPressed: () 
                          Navigator.pushNamed(context, NewLogScreen.id);
                        ,
                        constraints: BoxConstraints.tight(Size(60, 60)),
                        child: Icon(
                          Icons.add,
                          color: Color.fromRGBO(255, 255, 255, 1),
                          size: 30,
                        ),
                        shape: CircleBorder(),
                        fillColor: Color.fromRGBO(46, 54, 143, 1),
                        padding: EdgeInsets.all(15.0),
                        elevation: 1,
                      ),
                      SizedBox(
                        height: 10,
                      ),
                      Text(
                        'Add log',
                        style: kAddLogLabel,
                      )
                    ],
                  ),
                ),
              ]),
          list.isEmpty || chartData.isEmpty
              ? Expanded(
                  child: Center(
                    child: Text("No Log Data.."),
                  ),
                )
              : Expanded(
                  child: Column(
                    mainAxisSize: MainAxisSize.max,
                    children: <Widget>[
                      Container(
                        height: 150,
                        alignment: Alignment.center,
                        child: LineChartWidget(
                          list: chartData,
                          isDollar: true,
                        ),
                      ),
                      SizedBox(
                        height: 10,
                      ),
                      MultiSelectChip(
                        onSelectionChanged: (selectedList) async 
                          setState(() 
                            selectedItems = selectedList;
                          );
                          await Provider.of<LogProvider>(context, listen: false)
                              .filterLogList(selectedItems);
                        ,
                      ),
                      MonthSelect(Color.fromRGBO(246, 246, 246, 1),
                          onMonthSelectionChanged: (selected) 
                        setState(() 
                          selectedMonth = selected;
                        );
                        selectedMonth == null
                            ? refreshData(currentMonth)
                            : refreshData(selectedMonth);
                      ),
                      Padding(
                        padding:
                            const EdgeInsets.only(top: 10, left: 0, right: 0),
                        child: Container(
                          width: double.infinity,
                          height: 1.0,
                          color: kDividerColor,
                        ),
                      ),

我看到的是 Multiselect Chip 有相同的项目列表被重绘/添加到列表视图 3 次,每次我进入 NewLog 屏幕时,列表都会不断增长

我目前在 4 个不同的屏幕上使用相同的小部件,但由于某种原因,当我导航到另一个屏幕时,列表会重置并显示原始项目,而重复的项目消失

在离开屏幕时,我可以做些什么来防止重绘

谢谢

【问题讨论】:

【参考方案1】:

您是否尝试过在Provider.of() 中指定listen: falsedidChangeDependencies() 中使用?或许能解决问题。

但是,仍然存在风险。我怀疑在那里初始化某些东西是安全的,因为当/每当 State 对象的依赖项发生变化时调用 didChangeDependencies(),正如其 document 中所写的那样。在initState() 中执行此操作会更安全,或者仅在外部执行一次并将其结果传递给 MultiSelectChip。

【讨论】:

我有另一个管理 clinincList 提供程序类的屏幕,所以当我将另一个项目添加到列表中时,小部件会更新并将额外的项目添加到列表中,所以我需要保持监听为真,我尝试将列表传递到小部件中 传入列表并在 initState 上加载已解决此问题谢谢!

以上是关于跨多个屏幕使用的 Flutter Stateful Widget 正在重建的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 中 stateless 和 stateful widget 的区别[Flutter专题60]

Flutter控件篇(Stateful widget)——ListView

从 Flutter 中的 Stateful Widget 返回数据

Flutter Stateful Widget 状态未初始化

Flutter - Stateful(有状态) 和 stateless(无状态) widgets

Flutter Stateful Widget 重新创建 State