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

时间:2019-08-16 18:22:13

标签: flutter dart

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

作为一个例子,在下面的结构中,我可能需要轻按Text("every")才能打开相邻DropdownButton的下拉列表,其行为类似于单击<select>的HTML标签。

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,
    ),
  ),
]);

注意:尽管我需要解决此问题的一般解决方案,但我不仅需要如何使Text表现得像HTML标签一样在下面的树中。可能需要通过进一步的“离开”按钮等触发它才能打开。

2 个答案:

答案 0 :(得分:2)

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

最简单的方法,无需修改SDK,即可完成所需的工作,复制 dropdown.dart 并创建自己的版本,例如 custom_dropdown.dart ,并将代码粘贴在那里...

在546行中,将类重命名为 CustomDropdownButton ,在660和663行中,将_DropdownButtonState重命名为 CustomDropdownButtonState ,(我们需要将状态类公开到文件外部)。

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

不是公开_handleTap()并重构代码,而是添加另一种方法,例如:

(line 726)
void callTap() => _handleTap();

现在,将代码更改为使用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();
    });
  }

答案 1 :(得分:0)

另一个答案是执行此操作的最佳方法,但是按照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')),
      ],
    );
  }
}

2。使用Actions.invoke

Flutter的最新功能之一是Actions(我不确定它的用途,我只是在flutter upgrade之后才注意到它),DropdownButton使用了它对不同的反应...好吧,行动。

因此,触发按钮的方法要简单一点,那就是找到Actions小部件的上下文并调用必要的操作。

此方法有两个优点:首先,Actions小部件在树中较高,因此遍历该树的时间不会像GestureDetector那样长,其次,{{ 1}}似乎比手势检测更通用,因此将来从Actions消失的可能性较小。

DropdownButton