【问题标题】:Reading State of a Stateful Widget读取有状态小部件的状态
【发布时间】:2018-10-10 02:26:06
【问题描述】:

我一直在缓慢地使用 Flutter 构建应用程序,并且正在努力在 StatefulWidget/State 范式下工作。

我习惯于从其他类中读取或更改某些 UI 组件的状态,但不太了解如何在 Flutter 中执行此操作。例如,现在我正在构建一个包含 CheckBoxListTiles 的 ListView。我想从另一个班级查看并阅读列表图块中每个复选框的状态。

我觉得这在实践中应该非常简单,但我无法想出一种方法或示例来有效地将给定 Widget 的状态外部化,以便可以在其他地方使用它。我知道在明显的困难背后很可能有一个有意的和哲学的原因,并且我自己可以看到为什么它在诸如 Flutter 这样的框架中至关重要。但是,我觉得受到限制,因为对于新手来说,如何立即在范式下进行工作并不明显。

如何从 ItemsList 类中读取复选框的值?

物品类:

class ListItem extends StatefulWidget {

  String _title = "";
  bool _isSelected = false;

  ListItem(this._title);

  @override
  State<StatefulWidget> createState() => new ListItemState(_title);
}

class ListItemState extends State<ListItem> {

  String _title = "";
  bool _isSelected = false;

  ListItemState(this._title);

  @override
  Widget build(BuildContext context) {

    return new CheckboxListTile(

      title: new Text(_title),
      value: _isSelected,
      onChanged: (bool value) {
        setState(() {
          _isSelected = value;
        });
      },

    );

  }

  String getTitle() {
    return _title;
  }

  bool isSelected() {
    return _isSelected;
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

}

还有列表类:

class ItemsList extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => new ItemsListState();

}

class ItemsListState extends State<ItemsList> {

  List<ListItem> _items = new List();

  @override
  Widget build(BuildContext context) {

    return new Scaffold(

      body: new ListView(
        children: _items.map((ListItem item) {
          return item;
        }).toList(),
      ),

      floatingActionButton: new FloatingActionButton(
        onPressed: addItem,
        child: new Icon(Icons.add),),

    );
  }

  void addItem() {
    setState(() {
      _items.add(new ListItem("Item"));
    });
  }

  List<String> getSelectedTitles() {
    List<String> selected = new List();

        ***This is where I would like to externalize the state***

    for(ListItem e in _items) {
      if(e.isSelected()) {
        selected.add(e.getTitle());
      }
    }

    return selected;
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

}

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    Flutter 与您常用的应用不同。它使用类似于 react 的原理,它们本身类似于函数式编程。

    这意味着颤振带有一组限制,迫使您以不同的方式构建应用程序:

    • 小部件是不可变的。你不更新它们。您重新创建它们。
    • 您无法访问您孩子的状态。您只能访问父母的数据/状态。

    作为奖励,您几乎可以肯定任何事件不会对另一个不相关的类产生隐含的未知后果。


    好的,但是这对我的示例有何改变?

    这很简单:这意味着对于一个列表来说,不可能从其中一个列表项中访问信息。

    相反,您应该做的是:将所有列表项的信息存储在列表中。并将其传递给每个人。

    所以首先我们需要更改ListItem 以便它从它的父级获取所有内容。我们也可以让它无状态,因为它不再存储任何信息。

    有了这些,ListItem 突然变得非常简单:

    class ListItem extends StatelessWidget {
      final String title;
      final bool isSelected;
      final ValueChanged<bool> onChange;
    
      ListItem({ @required this.onChange, this.title = "", this.isSelected = false, });
    
      @override
      Widget build(BuildContext context) {
        return new CheckboxListTile(
          title: new Text(title),
          value: isSelected,
          onChanged: onChange
        );
      }
    }
    

    请注意,所有字段都是不可变的。并且传递给CheckboxListTileonChange 事件也作为参数传递!

    此时您可能会想“但是现在那个课程不是没有意义吗”? 并不真地。在我们的例子中,布局非常简单。但这是创建此类类的好习惯,因为它会拆分布局。为布局逻辑清理父代码。

    不管怎样,让我们​​继续吧。

    现在,让我们修改父对象,使其包含信息并将其传递下去:

    class ItemsList extends StatefulWidget {
      State<StatefulWidget> createState() => new ItemsListState();
    }
    
    class ItemsListState extends State<ItemsList> {
      List<ListItem> items = new List<ListItem>();
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          body: new ListView(
            children: items,
          ),
          floatingActionButton: new FloatingActionButton(
            onPressed: addItem,
            child: new Icon(Icons.add),
          ),
        );
      }
    
      void addItem() {
        final listItemIndex = items.length;
        final newList = new List<ListItem>.from(items)
          ..add(
            new ListItem(
              onChange: (checked) => onListItemChange(listItemIndex, checked),
              title: "Item n' $listItemIndex",
              isSelected: false,
            ),
          );
    
        this.setState(() {
          items = newList;
        });
      }
    
      onListItemChange(int listItemIndex, bool checked) {
        final newList = new List<ListItem>.from(items);
        final currentItem = items[listItemIndex];
        newList[listItemIndex] = new ListItem(
          onChange: currentItem.onChange,
          isSelected: checked,
          title: currentItem.title,
        );
    
        this.setState(() {
          items = newList;
        });
      }
    }
    

    请注意这里我不是改变 items 列表,而是每次都创建一个新列表。

    为什么会这样?那是因为,就像我之前解释的那样,字段应该是不可变的。 ItemsList 可能是有状态的,但这不是不遵循该原则的理由。因此,我们不是编辑列表,而是在旧列表的基础上创建一个新列表。

    事实是,如果没有那个新列表ListView 会认为“嘿,你给我发的列表和以前一样。所以我不需要刷新对吗?”。


    现在很酷的是,默认情况下ItemsList 拥有关于ListItem 的所有信息。所以getSelectedTitles 变得很容易做到:

    Iterable<String> getSelectedTitles() {
      return items.where((item) => item.isSelected).map((item) => item.title);
    }
    

    【讨论】:

      猜你喜欢
      • 2021-02-16
      • 2021-12-15
      • 2021-06-11
      • 1970-01-01
      • 2020-10-19
      • 2018-09-01
      • 2020-03-10
      • 1970-01-01
      • 2021-09-21
      相关资源
      最近更新 更多