本文介绍了在 Flutter 中,如何在点击其他小部件时打开 DropdownButton?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to have a DropdownButton's list of options open/show programmatically when some other widget is tapped. I know that this may not be UI-best-practice and all, but I need this behavior:

举个例子,在像下面这样的结构中,我可能需要点击 Text("every") 来打开相邻的 DropdownButton 的下拉列表, 行为类似于在 HTML 中单击 的标签.

As an example, in a structure like the one below, I may need to have taping Text("every") to open the neighboring DropdownButton's dropdown list, behaviors similar to clicking a <select>'s label in 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 标签"在下面的树中.它可能需要通过更远的按钮等触发才能打开.

NOTE: I am in need though of the general solution to this problem, not just how to make that Text behave somewhat "like a HTML label" in the tree below. It may need to be triggered to open by maybe a further away button etc.

推荐答案

另一个答案是最好的方法,但是根据 OP 在评论中的要求,这里有两种非常hacky"的方法来实现这一点,但无需实现自定义小部件.

The other answer is the best way to do this, but as requested by the OP in comments, here are two very "hacky" ways to achieve this, yet without implementing custom widgets.

1.使用 GlobalKey

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

If we look at the source code of DropdownButton, we can notice that it uses GestureDetector to handle taps. However, it's not a direct descendant of DropdownButton, and we cannot depend on tree structure of other widgets, so the only reasonably stable way to find the detector is to do the search recursively.

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

One example is worth a thousand explanations:

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 使用它来对不同的......好吧,动作做出反应.

One of the recent features of Flutter is Actions (I'm not sure what it's meant for, I've only noticed it today after flutter upgrade), and DropdownButton uses it for reacting to different... well, actions.

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

So a little tiny bit less hacky way to trigger the button would be to find the context of Actions widget and invoke the necessary action.

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

There are two advantages of this approach: firstly, Actions widget is a bit higher in the tree, so traversing that tree wouldn't be as long as with GestureDetector, and secondly, Actions seems to be a more generic mechanism than gesture detection, so it's less likely to disappear from DropdownButton in the future.

// 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;
          });
        }
      });
    }
  });
}

这篇关于在 Flutter 中,如何在点击其他小部件时打开 DropdownButton?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-14 15:06