如何在小部件方法中将变量作为状态传递?

How to pass variables as state in widget methods?

我有一段代码可以在 Flutter 中呈现 DropdownButton。我需要几个下拉列表,所以我将它提取到一个方法中以调用我需要的每个实例。我在其中传递了所选值的状态变量,以及所有选项。在 DropdownButton 内部,我有在 DropdownButton onChanged 方法中更新 selected 的方法。

因此,当我调用该方法而不是就地使用它时,问题就出现了,因为 onChanged 和 onClear 的函数不会重建小部件,因为它们不会更新调用之外的状态变量下拉方法。做这个的最好方式是什么?如果我从外部传入函数,它确实有效,但我将不得不复制一些代码。我应该将其重写为 StatefulWidget 吗?

DropdownButton myDropdown(
  String selected,
  List<dynamic> options, {
  Function onChanged,
  Function onClear,
}) {
  return DropdownButton<String>(
    value: selected,
    // onChanged: (String newValue) { // Not working
    //   setState(() {
    //     selected = newValue;
    //   });
    // },
    onChanged: onChanged,
    selectedItemBuilder: (BuildContext context) {
      return options?.map<Widget>((dynamic item) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            Text(item),
            IconButton(
              icon: Icon(Icons.clear),
              // onPressed: () { // Not working
              //   setState(() {
              //     selected = null;
              //   });
              // },
              onPressed: onClear,
            ),
          ],
        );
      })?.toList() ?? [];
    },
    items: options?.map<DropdownMenuItem<String>>((dynamic value) {
      return DropdownMenuItem<String>(
        value: value,
        child: Text(value),
      );
    })?.toList() ?? [],
  );
}

(在项目中我确实使用了provider来进行状态管理,但我不确定它是否真的与这个问题相关。)

在您调用 DropdownButton

的父级 class 的某处定义 selected
var selected;

现在这样写DropdownButton -

DropdownButton(
 selected,
 [whatever],
 onChanged: (newValue) {
    selected = newValue;
    setState(() {});
 } 
 onClear: () {
  selected = null;
  setState(() {}); 
}
)

因此,一旦调用 onChanged 或 onClear,它就会更改 selected 值并使用新选择的值刷新小部件。

解决方案是通过将 DropdownButton 的值包装在 StatefulWidget 中来管理它的状态。

我已经提供了一个自定义示例 Widget 来实现此目的,本着与您的原始代码相同的精神。


import 'package:flutter/material.dart';

/// A managed [DropdownButton] initialized to [selected] with possible values
/// [options].
///
/// Selected value is updated on dropdown item clicks, and triggers [onChanged]
/// callback.
///
/// An "x" clear button is present on all menu items, and sets selected item to
/// null, while also triggering [onClear] callback.
class SuperDropdown extends StatefulWidget {
  SuperDropdown({
    @required this.selected,
    @required this.options,
    this.onClear,
    this.onChanged,
  });
  final Function(String s) onChanged;
  final Function() onClear;
  final List<String> options;
  final String selected;

  @override
  _SuperDropdownState createState() => _SuperDropdownState();
}

class _SuperDropdownState extends State<SuperDropdown> {
  String selected;

  List<DropdownMenuItem> get items => widget.options
      .map((i) => DropdownMenuItem(
            value: i,
            child: Text(i),
          ))
      .toList();

  void onChanged(String value) {
    setState(() => selected = value);
    widget.onChanged?.call(value);
  }

  void onClear() {
    setState(() => selected = null);
    widget.onClear?.call();
  }

  List<Widget> itemBuilder(BuildContext context) => widget.options
      .map((i) =>
          Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
            Text(i),
            IconButton(
              icon: Icon(Icons.clear),
              onPressed: onClear,
            ),
          ]))
      .toList();
  @override
  void initState() {
    super.initState();
    selected = widget.selected;
  }

  @override
  Widget build(BuildContext context) => DropdownButton<String>(
        value: selected,
        items: items,
        onChanged: onChanged,
        selectedItemBuilder: itemBuilder,
      );
}

void main() => runApp(
      MaterialApp(
        home: Scaffold(
          body: Center(
            child: Column(
              children: ["abc", "def"]
                  .map((letters) => SuperDropdown(
                        selected: letters.substring(0, 1),
                        options: letters.split(''),
                        onChanged: (s) =>
                            print("dropdown $letters changed to $s"),
                        onClear: () => print("dropdown $letters cleared"),
                      ))
                  .toList(),
            ),
          ),
        ),
      ),
    );