在我的Ffltter应用程序中,我有一个屏幕,它是一个MaterialApp,上面有一个Scaffold小部件,就像它的家一样.

这个Scaffold的appBar属性是一个appBar小部件,其actions属性中填充了一些操作,并有一个弹出菜单来容纳其余的选项.

问题是,据我所知,AppBar actions列表的子级可以是通用小部件(它将作为操作添加),也可以是PopupMenuButton的实例,在这种情况下,它将添加特定于平台的图标,当触发该图标时,将打开AppBar弹出菜单.

在原生Android上,这不是它的工作方式.我只需要inflating 一个充满菜单项的菜单,每个菜单项都可以被强制为一个动作,强制不是一个动作,或者有一个特殊的值"ifRoom",这意味着"如果有空间就是一个动作,否则就是弹出菜单中的一个项目".

在Ffltter中,有没有一种方法可以在不编写复杂逻辑来填充AppBar的"Actions"属性的情况下具有此行为?

我研究过AppBar和PopupMenuButton文档,但到目前为止,还没有解释如何做这样的事情.我可以模拟这种行为,但接下来我必须实际编写一个 routine 来计算可用空间,并相应地构建"操作"列表.

这是一个典型的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将水平扩展并将这些项作为操作放置.

推荐答案

开箱即用不支持此操作,但您可以复制该行为.我已经创建了以下小部件:

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,其中showAsActionALWAYSIF_ROOM,并且始终为showAsActionIF_ROOMNEVER的操作提供overflowWidget.

以下是使用上述代码的结果:

enter image description here enter image description here

您可以根据自己的要求定制CustomActionsRow,例如,您可能有不同宽度的操作,在这种情况下,您希望每个CustomAction提供自己的宽度等.

Flutter相关问答推荐

关闭视频通话,相机仍在运行!(Flutter WebRTC)

Box约束强制无限高:SizedBox. Expand()

Flutter—如何在Riverpod中使用全类状态?

创建后检索FiRestore文档ID

如果文档确实存在,则FiRestore将";False";返回到";if-Document-Existes";...还是我说错了?

在创建肘部并使用冻结时,无法使用中的Copy With方法

如何在Flutter 中使用滚动展开窗口小部件

Flutter Xcode 15 错误(Xcode):DT_TOOLCHAIN_DIR 无法用于判断 LIBRARY_SEARCH_PATHS

请求已在 flutter 中发送到 django 服务器,并且已返回响应,但条件仍然为 false

如何在flutter中将所有单词的第一个字母大写

尽管设置了对齐方式,如何对齐列中的行?

更新番茄计时器值不会触发 Flutter Riverpod 中的alert 声音

使用Flutter项目设置Firebase遇到困难?这些解决方案或许能帮到你!

通过在父窗口小部件功能上设置状态来浏览屏幕

Android 模拟器在 Apple M2 芯片 EXC_BAD_ACCESS / KERN_INVALID_ADDRESS 上随机崩溃

如果不刷新页面,我看不到从 Firestore 中提取的数据

无法列出 com.facebook.android:facebook-login -Facebook 登录问题的版本

如何将所有行项目平等地放在一行中?Flutter

从 Uint8List 或图像对象创建 InputImageData 用于 Google ML Kit 人脸检测

Flutter 评级栏包选中和未选中的容器