在 Flutter 中,如何在点击其他小部件时打开 DropdownButton?

Neu*_*onQ 16 dart flutter

DropdownButton当点击其他小部件时,我需要以编程方式打开/显示a的选项列表。我知道这可能不是 UI 最佳实践,但我需要这种行为:

例如,在如下所示的结构中,我可能需要通过录音Text("every")来打开相邻DropdownButton的下拉列表,其行为类似于<select>在 HTML 中单击 a的标签。

Row(children: [
  Padding(
    padding: const EdgeInsets.only(right: 16),
    child: Text('every'),
  ),
  Expanded(
    child: DropdownButton<String>(
      value: _data['every'],
      onChanged: (String val) => setState(() => _data['every'] = val),
      items: _every_options.map<DropdownMenuItem<String>>(
        (String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        },
      ).toList(),
      isExpanded: true,
    ),
  ),
]);
Run Code Online (Sandbox Code Playgroud)

注意:我需要这个问题一般解决方案,而不仅仅是如何Text在下面的树中使其表现得有点“像 HTML 标签”。它可能需要通过更远的按钮等触发才能打开。

roi*_*ker 9

这是(许多)设计的 API 限制之一......

完成您想要的最简单方法,无需修改 SDK,复制dropdown.dart,并创建您自己的版本,比如说custom_dropdown.dart,然后将代码粘贴到那里...

在第 546 行,将类重命名为CustomDropdownButton,在第 660 和 663 行将_DropdownButtonState重命名为CustomDropdownButtonState,(我们需要将状态类暴露在文件外)。

现在你可以用它做任何你想做的事情,虽然你对 _handleTap() 感兴趣,但可以打开覆盖菜单选项。

与其将 _handleTap() 设为公开并重构代码,不如添加另一个方法,例如:

(line 726)
void callTap() => _handleTap();
Run Code Online (Sandbox Code Playgroud)

现在,更改您的代码以使用 DropdownButton 而不是 Flutter 的 DropdownButton,关键是“设置键”(全局一个):P

// 一些有状态的小部件实现。

  Map<String, String> _data;
  List<String> _every_options;
  // we need the globalKey to access the State.
  final GlobalKey dropdownKey = GlobalKey();

  @override
  void initState() {
    _every_options = List.generate(10, (i) => "item $i");
    _data = {'every': _every_options.first};
    simulateClick();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Row(children: [
        Padding(
          padding: const EdgeInsets.only(right: 16),
          child: Text('every'),
        ),
        Expanded(
          child: CustomDropdownButton<String>(
            key: dropdownKey,
            value: _data['every'],
            onChanged: (String val) => setState(() => _data['every'] = val),
            items: _every_options
                .map((str) => DropdownMenuItem(
                      value: str,
                      child: Text(str),
                    ))
                .toList(),
            isExpanded: true,
          ),
        ),
      ]),
    );
  }

  void simulateClick() {
    Timer(Duration(seconds: 2), () {
      // here's the "magic" to retrieve the state... not very elegant, but works.
      CustomDropdownButtonState state = dropdownKey.currentState;
      state.callTap();
    });
  }
Run Code Online (Sandbox Code Playgroud)


mrn*_*ver 9

另一个答案是最好的方法,但正如 OP 在评论中所要求的那样,这里有两种非常“hacky”的方法来实现这一点,但没有实现自定义小部件。

1.DropdownButton直接使用访问小部件树GlobalKey

如果我们查看 的源代码DropdownButton,我们可以注意到它用于GestureDetector处理点击。然而,它不是 的直接后代DropdownButton,我们不能依赖于其他小部件的树结构,所以找到检测器的唯一合理稳定的方法是递归搜索。

一个例子胜过一千个解释:

class DemoDropdown extends StatefulWidget {  
  @override
  InputDropdownState createState() => DemoDropdownState();
}

class DemoDropdownState<T> extends State<DemoDropdown> {
  /// This is the global key, which will be used to traverse [DropdownButton]s widget tree
  GlobalKey _dropdownButtonKey;

  void openDropdown() {
    GestureDetector detector;
    void searchForGestureDetector(BuildContext element) {
      element.visitChildElements((element) {
        if (element.widget != null && element.widget is GestureDetector) {
          detector = element.widget;
          return false;

        } else {
          searchForGestureDetector(element);
        }

        return true;
      });
    }

    searchForGestureDetector(_dropdownButtonKey.currentContext);
    assert(detector != null);

    detector.onTap();
  }

  @override
  Widget build(BuildContext context) {
    final dropdown = DropdownButton<int>(
      key: _dropdownButtonKey,
      items: [
        DropdownMenuItem(value: 1, child: Text('1')),
        DropdownMenuItem(value: 2, child: Text('2')),
        DropdownMenuItem(value: 3, child: Text('3')),
      ],
      onChanged: (int value) {},
    );

    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Offstage(child: dropdown),
        FlatButton(onPressed: openDropdown, child: Text('CLICK ME')),
      ],
    );
  }
}

Run Code Online (Sandbox Code Playgroud)

2. 使用 Actions.invoke

Flutter 的最新功能之一是Actions(我不确定它的含义,我今天才注意到它flutter upgrade),并DropdownButton使用它来对不同的......好吧,动作做出反应。

因此,触发按钮的稍微不那么笨拙的方法是找到Actions小部件的上下文并调用必要的操作。

这种方法有两个优点:首先,Actions小部件在树中的位置稍高,因此遍历该树不会像 with 一样长GestureDetector,其次,Actions似乎是一种比手势检测更通用的机制,因此不太可能从DropdownButton未来消失。

// The rest of the code is the same
void openDropdown() {
  _dropdownButtonKey.currentContext.visitChildElements((element) {
    if (element.widget != null && element.widget is Semantics) {
      element.visitChildElements((element) {
        if (element.widget != null && element.widget is Actions) {
          element.visitChildElements((element) {
            Actions.invoke(element, Intent(ActivateAction.key));
            return false;
          });
        }
      });
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

  • 方法 #2 通过修改后的调用 `Actions.invoke(element, const ActivateIntent());` 对我有用 (2认同)