当其他一些窗口小部件被点击时,我需要以编程方式打开/显示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标签一样在下面的树中。可能需要通过进一步的“离开”按钮等触发它才能打开。
答案 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