跳转至正文

平台惯用法

了解如何创建能响应屏幕尺寸变化的响应式应用。

自适应应用还需考虑的最后一块是平台标准。各平台有自己的惯用法与规范;这些名义上或事实上的标准塑造用户对应用应如何行为的预期。部分得益于 Web,用户已习惯更定制的体验,但体现这些平台标准仍能带来显著好处:

  • 降低认知负担
    通过匹配用户既有心智模型,完成任务更直观,减少思考、提升效率并降低挫败感。

  • 建立信任
    应用不符合预期时用户可能警惕或怀疑。反之,熟悉的 UI 能建立信任并提升质量感知,往往还能带来更好的应用商店评分——我们都乐见其成!

考虑各平台的预期行为

#

第一步是花时间思考该平台上的预期外观、呈现或行为。尽量忘记当前实现的限制,只设想理想用户体验,再倒推实现。

另一种思考方式是问:「该平台用户会如何预期达成此目标?」然后设想在你的应用中如何无妥协地实现。

若你不是该平台的常用用户,这可能较难。你可能不了解具体惯用法并完全遗漏。例如,长期使用 Android 的用户可能不了解 iOS 平台惯例,macOS、Linux、Windows 同理。这些差异对你可能细微,对资深用户却可能非常明显。

寻找平台倡导者

#

若可能,为每个平台指定一名倡导者。理想情况下,倡导者以该平台为主要设备,并能提供挑剔用户的视角。为减少人数可合并角色:一人负责 Windows 与 Android,一人负责 Linux 与 Web,一人负责 Mac 与 iOS。

目标是获得持续、专业的反馈,使应用在各平台都出色。应鼓励倡导者挑剔,指出与设备上典型应用的差异。简单例子:对话框默认按钮在 Mac 与 Linux 通常在左侧,Windows 则在右侧。若不常用某平台,此类细节易被忽略。

保持独特

#

符合预期行为并不意味着必须使用默认 widget 或样式。许多热门多平台应用有鲜明、主观的 UI,包括自定义按钮、上下文菜单与标题栏。

跨平台整合样式与行为越多,开发与测试越轻松。诀窍是在打造强识别度的独特体验与尊重各平台规范之间取得平衡。

常见惯用法与规范

#

快速浏览你可能要考虑的若干规范与惯用法,以及在 Flutter 中的实现思路。

滚动条外观与行为

#

桌面与移动用户都预期有滚动条,但各平台行为不同。移动用户预期较小、仅在滚动时出现的滚动条;桌面用户通常预期常驻、较大且可点击或拖动的滚动条。

Flutter 自带 Scrollbar widget,已根据当前平台支持自适应颜色与尺寸。你可能只需在桌面平台切换 alwaysShown

dart
return Scrollbar(
  thumbVisibility: DeviceType.isDesktop,
  controller: _scrollController,
  child: GridView.count(
    controller: _scrollController,
    padding: const EdgeInsets.all(Insets.extraLarge),
    childAspectRatio: 1,
    crossAxisCount: colCount,
    children: listChildren,
  ),
);

这些细节关注能让应用在给定平台上更舒适。

多选

#

列表内多选是另一存在跨平台细微差异的领域:

dart
static bool get isSpanSelectModifierDown =>
    isKeyDown({LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight});

要进行平台感知的 Control 或 Command 检查,可编写类似代码:

dart
static bool get isMultiSelectModifierDown {
  bool isDown = false;
  if (Platform.isMacOS) {
    isDown = isKeyDown({
      LogicalKeyboardKey.metaLeft,
      LogicalKeyboardKey.metaRight,
    });
  } else {
    isDown = isKeyDown({
      LogicalKeyboardKey.controlLeft,
      LogicalKeyboardKey.controlRight,
    });
  }
  return isDown;
}

键盘用户还需考虑 全选 操作。若有大量可选列表项,许多键盘用户会预期可用 Control+A 全选。

触控设备

#

在触控设备上,多选通常更简单,预期行为类似桌面按住 isMultiSelectModifier。可用单击选择或取消选择,通常还有 全选清除 当前选择的按钮。

不同设备上的多选处理取决于具体用例,重要的是为各平台提供最佳交互模型。

可选中文本

#

Web(以及程度较轻的桌面)上常见预期是:大部分可见文字可用鼠标选中。文字不可选时,Web 用户往往反感。

幸运的是,用 SelectableText widget 即可轻松支持:

dart
return const SelectableText('Select me!');

要支持富文本,请使用 TextSpan

dart
return const SelectableText.rich(
  TextSpan(
    children: [
      TextSpan(text: 'Hello'),
      TextSpan(
        text: 'Bold',
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
    ],
  ),
);

标题栏

#

在现代桌面应用中,常见做法是自定义应用窗口标题栏,添加 Logo 强化品牌或上下文控件以节省主 UI 垂直空间。

Samples of title bars

Flutter 不直接支持,但可用 bits_dojo package 禁用原生标题栏并替换为自定义实现。

该 package 底层使用纯 Flutter widget,可在 TitleBar 中添加任意 widget,便于在应用不同区域导航时适配标题栏。

上下文菜单与工具提示

#

在桌面上,若干交互以叠加层中的 widget 呈现,但触发、关闭与定位方式不同:

  • 上下文菜单
    通常由右键触发,靠近鼠标定位,通过任意点击、选择菜单项或点击外部关闭。

  • Tooltip(工具提示)
    通常在交互元素上悬停 200–400ms 触发,一般锚定到 widget(而非鼠标位置),鼠标离开该 widget 时关闭。

  • 弹出面板(亦称 flyout)
    与工具提示类似,通常锚定到 widget。主要区别是面板多在点击时显示,光标离开通常不会自动隐藏,而是通过点击面板外部或按 关闭 / 提交 按钮关闭。

在 Flutter 中显示基本工具提示,请使用内置 Tooltip widget:

dart
return const Tooltip(
  message: 'I am a Tooltip',
  child: Text('Hover over the text to show a tooltip.'),
);

Flutter 在编辑或选择文字时也提供内置上下文菜单。

要显示更高级的工具提示、弹出面板或创建自定义上下文菜单,可使用现有 package,或用 StackOverlay 自行构建。

部分可用 package 包括:

这些控件对触控用户可作为加速器很有价值,对鼠标用户则必不可少。他们预期右键、就地编辑、悬停获取更多信息。未满足这些预期会令用户失望,或至少觉得「哪里不对」。

水平按钮顺序

#

在 Windows 上,一行按钮中确认按钮在行首(左侧)。其他平台相反,确认按钮在行尾(右侧)。

在 Flutter 中可用 RowTextDirection 属性轻松处理:

dart
TextDirection btnDirection = DeviceType.isWindows
    ? TextDirection.rtl
    : TextDirection.ltr;
return Row(
  children: [
    const Spacer(),
    Row(
      textDirection: btnDirection,
      children: [
        DialogButton(
          label: 'Cancel',
          onPressed: () => Navigator.pop(context, false),
        ),
        DialogButton(
          label: 'Ok',
          onPressed: () => Navigator.pop(context, true),
        ),
      ],
    ),
  ],
);

Sample of embedded image

Sample of embedded image

#

桌面应用另一常见模式是菜单栏。 Windows 与 Linux 上菜单是 Chrome 标题栏的一部分, macOS 上则位于主屏幕顶部。

目前可用原型插件指定自定义菜单栏项,预计该功能最终会并入主 SDK。

值得一提的是,Windows 与 Linux 上无法将自定义标题栏与菜单栏并存。创建自定义标题栏会完全替换原生标题栏,因而也会失去集成的原生菜单栏。

若同时需要自定义标题栏与菜单栏,可在 Flutter 中实现,类似自定义上下文菜单。

拖放

#

触控与指针输入的核心交互之一是拖放。两种输入都预期有此交互,但在可滚动可拖放列表上存在重要差异。

一般而言,触控用户预期看到拖动手柄以区分可拖区域与可滚动区域,或通过长按启动拖动,因为滚动与拖动共用单指输入。

鼠标用户输入选项更多,可用滚轮或滚动条滚动,通常无需专用拖动手柄。 macOS Finder 或 Windows 资源管理器即如此:选中项即可拖动。

在 Flutter 中可用多种方式实现拖放。具体实现超出本文范围,高层选项包括: