问题描述
我需要在点击其他小部件时以编程方式打开/显示 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!