在我的Flutter应用程序中,我有一个屏幕,它是一个MaterialApp,其中包含一个脚手架小部件。
此Scaffold的appBar属性是一个AppBar小部件,其中的actions属性填充了一些操作,并带有一个弹出菜单来容纳其余选项。
问题是,据我了解,AppBar actions
列表的子级可以是通用小部件(将作为操作添加)或PopupMenuButton
的实例,在这种情况下,它将添加特定于平台的图标,该图标在触发时会打开AppBar弹出菜单。
在本机Android上不是这样。我只需要填充一个填充有菜单项的菜单,就可以将每个项强制为一个动作,强制为非动作,或者具有特殊值“ ifRoom”,即“如果有空间则为一个动作,否则为弹出菜单中的一个项目”。
Flutter中是否有一种方法可以使这种行为发生,而不必编写复杂的逻辑来填充AppBar的“ actions”属性?
我已经研究了AppBar和PopupMenuButton文档,到目前为止,没有任何内容说明如何做这种事情。我可以模拟这种行为,但是随后我必须实际编写一个例程来计算可用空间并相应地构建“动作”列表。
这是一个典型的Android菜单,其中混合了操作和弹出菜单项。请注意,如果有空间,“ load_game”条目可以是一个动作,否则将成为菜单条目。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_game"
android:icon="@drawable/ic_new_game"
android:title="@string/new_game"
android:showAsAction="always"/>
<item android:id="@+id/load_game"
android:icon="@drawable/ic_load_game"
android:title="@string/load_game"
android:showAsAction="ifRoom"/>
<item android:id="@+id/help"
android:icon="@drawable/ic_help"
android:title="@string/help"
android:showAsAction="never" />
</menu>
另一方面,在Flutter中,我必须提前决定选项是操作还是菜单项。
AppBar(
title: Text("My Incredible Game"),
primary: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
tooltip: "New Game",
onPressed: null,
),
IconButton(
icon: Icon(Icons.cloud_upload),
tooltip: "Load Game",
onPressed: null,
),
PopupMenuButton(
itemBuilder: (BuildContext context) {
return <PopupMenuEntry>[
PopupMenuItem(
child: Text("Help"),
),
];
},
)
],
)
我希望能起作用的是AppBar实际上只有一个“ action”属性,而不是“ actions”。该属性只是一个允许我拥有任何东西的小部件,因此,如果我只想要一个动作列表,则用IconButton填充的行就足够了。
与此同时,PopupMenuButton内的每个PopupMenuItem都将具有“ showAsAction”属性。如果已将PopupMenuButton内的一个或多个PopupMenuItem检查为动作或“ ifRoom”,并且有空间,则PopupMenuButton将水平展开并将这些项作为动作放置。
答案 0 :(得分:5)
开箱即用不支持此操作,但是您可以复制该行为。我创建了以下小部件:
import 'package:flutter/material.dart';
class CustomActionsRow extends StatelessWidget {
final double availableWidth;
final double actionWidth;
final List<CustomAction> actions;
const CustomActionsRow({
@required this.availableWidth,
@required this.actionWidth,
@required this.actions,
});
@override
Widget build(BuildContext context) {
actions.sort(); // items with ShowAsAction.NEVER are placed at the end
List<CustomAction> visible = actions
.where((CustomAction customAction) => customAction.showAsAction == ShowAsAction.ALWAYS)
.toList();
List<CustomAction> overflow = actions
.where((CustomAction customAction) => customAction.showAsAction == ShowAsAction.NEVER)
.toList();
double getOverflowWidth() => overflow.isEmpty ? 0 : actionWidth;
for (CustomAction customAction in actions) {
if (customAction.showAsAction == ShowAsAction.IF_ROOM) {
if (availableWidth - visible.length * actionWidth - getOverflowWidth() > actionWidth) { // there is enough room
visible.insert(actions.indexOf(customAction), customAction); // insert in its given position
} else { // there is not enough room
if (overflow.isEmpty) {
CustomAction lastOptionalAction = visible.lastWhere((CustomAction customAction) => customAction.showAsAction == ShowAsAction.IF_ROOM, orElse: () => null);
if (lastOptionalAction != null) {
visible.remove(lastOptionalAction); // remove the last optionally visible action to make space for the overflow icon
overflow.add(lastOptionalAction);
overflow.add(customAction);
} // else the layout will overflow because there is not enough space for all the visible items and the overflow icon
} else {
overflow.add(customAction);
}
}
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
...visible.map((CustomAction customAction) => customAction.visibleWidget),
if (overflow.isNotEmpty) PopupMenuButton(
itemBuilder: (BuildContext context) => [
for (CustomAction customAction in overflow) PopupMenuItem(
child: customAction.overflowWidget,
)
],
)
],
);
}
}
class CustomAction implements Comparable<CustomAction> {
final Widget visibleWidget;
final Widget overflowWidget;
final ShowAsAction showAsAction;
const CustomAction({
this.visibleWidget,
this.overflowWidget,
@required this.showAsAction,
});
@override
int compareTo(CustomAction other) {
if (showAsAction == ShowAsAction.NEVER && other.showAsAction == ShowAsAction.NEVER) {
return 0;
} else if (showAsAction == ShowAsAction.NEVER) {
return 1;
} else if (other.showAsAction == ShowAsAction.NEVER) {
return -1;
} else {
return 0;
}
}
}
enum ShowAsAction {
ALWAYS,
IF_ROOM,
NEVER,
}
您需要指出所有图标(包括溢出图标)的总可用宽度,以及每个动作的宽度(它们都必须具有相同的宽度)。这是一个使用它的示例:
return Scaffold(
appBar: AppBar(
title: const Text("Title"),
actions: <Widget>[
CustomActionsRow(
availableWidth: MediaQuery.of(context).size.width / 2, // half the screen width
actionWidth: 48, // default for IconButtons
actions: [
CustomAction(
overflowWidget: GestureDetector(
onTap: () {},
child: const Text("Never 1"),
),
showAsAction: ShowAsAction.NEVER,
),
CustomAction(
visibleWidget: IconButton(
onPressed: () {},
icon: Icon(Icons.ac_unit),
),
showAsAction: ShowAsAction.ALWAYS,
),
CustomAction(
visibleWidget: IconButton(
onPressed: () {},
icon: Icon(Icons.cancel),
),
overflowWidget: GestureDetector(
onTap: () {},
child: const Text("If Room 1"),
),
showAsAction: ShowAsAction.IF_ROOM,
),
CustomAction(
visibleWidget: IconButton(
onPressed: () {},
icon: Icon(Icons.ac_unit),
),
showAsAction: ShowAsAction.ALWAYS,
),
CustomAction(
visibleWidget: IconButton(
onPressed: () {},
icon: Icon(Icons.cancel),
),
overflowWidget: GestureDetector(
onTap: () {},
child: const Text("If Room 2"),
),
showAsAction: ShowAsAction.IF_ROOM,
),
CustomAction(
overflowWidget: GestureDetector(
onTap: () {},
child: const Text("Never 2"),
),
showAsAction: ShowAsAction.NEVER,
),
],
),
],
),
);
请注意,我没有添加任何断言,但是对于visibleWidget
为showAsAction
或ALWAYS
的操作,您应始终提供IF_ROOM
,并始终提供{ {1}}用于overflowWidget
是showAsAction
或IF_ROOM
的操作。
这是使用上面的代码的结果:
您可以根据需要自定义NEVER
,例如,您可能会执行不同宽度的操作,在这种情况下,您希望每个CustomActionsRow
都提供自己的宽度,等等。