跳至主要内容

自动平台适配

适配理念

#

通常,平台自适应性存在两种情况

  1. 操作系统环境的行为(例如文本编辑和滚动),如果发生不同的行为,则会被认为是“错误的”。
  2. 使用 OEM 的 SDK 在应用中传统实现的功能(例如在 iOS 上使用并行选项卡或在 Android 上显示 android.app.AlertDialog)。

本文主要介绍 Flutter 在 Android 和 iOS 上针对情况 1 提供的自动适配。

对于情况 2,Flutter 提供了产生平台约定适当效果的方法,但在需要应用设计选择时不会自动适配。有关讨论,请参阅 问题 #8410Material/Cupertino 自适应小部件问题定义

有关在 Android 和 iOS 上使用不同信息架构结构但共享相同内容代码的应用示例,请参阅 platform_design 代码示例

#

Flutter 提供了在 Android 和 iOS 上看到的导航模式,并自动将导航动画适配到当前平台。

#

Android 上,默认的 Navigator.push() 过渡模拟 startActivity(),后者通常具有一个自下而上的动画变体。

iOS

  • 默认的 Navigator.push() API 生成 iOS Show/Push 样式过渡,该过渡根据区域设置的 RTL 设置从末尾到开头进行动画处理。新路由后面的页面也会在与 iOS 相同的方向上视差滑动。
  • 当推送 PageRoute.fullscreenDialog 为 true 的页面路由时,存在单独的自下而上的过渡样式。这表示 iOS 的 Present/Modal 样式过渡,通常用于全屏模态页面。
An animation of the bottom-up page transition on Android
Android 页面过渡
An animation of the end-start style push page transition on iOS
iOS 推送过渡
An animation of the bottom-up style present page transition on iOS
iOS 显示过渡

特定于平台的过渡细节

#

Android 上,Flutter 使用 ZoomPageTransitionsBuilder 动画。当用户点击某个项目时,UI 会放大到显示该项目的屏幕。当用户点击返回时,UI 会缩小到上一个屏幕。

在使用推送样式过渡的 iOS 上,Flutter 的捆绑 CupertinoNavigationBarCupertinoSliverNavigationBar 导航栏会自动将每个子组件动画化到下一页或上一页的 CupertinoNavigationBarCupertinoSliverNavigationBar 上的对应子组件。

An animation of the page transition on Android
Android
An animation of the nav bar transitions during a page transition on iOS
iOS 导航栏

返回导航

#

Android 上,操作系统后退按钮默认情况下会发送到 Flutter 并弹出 WidgetsApp 的 Navigator 的顶部路由。

iOS 上,可以使用边缘滑动手势弹出顶部路由。

A page transition triggered by the Android back button
Android 后退按钮
A page transition triggered by an iOS back swipe gesture
iOS 后退滑动手势

滚动

#

滚动是平台外观和感觉的重要组成部分,Flutter 会自动调整滚动行为以匹配当前平台。

物理模拟

#

Android 和 iOS 都具有复杂的滚动物理模拟,难以用语言描述。通常,iOS 的可滚动内容具有更大的重量和动态摩擦力,但 Android 具有更大的静态摩擦力。因此,iOS 的速度逐渐提高,但停止时不会突然中断,并且在低速时更顺滑。

A soft fling where the iOS scrollable slid longer at lower speed than Android
轻弹比较
A medium force fling where the Android scrollable reached speed faster and stopped more abruptly after reaching a longer distance
中等弹力比较
A strong fling where the Android scrollable reach speed faster and reached significantly more distance
强弹力比较

过度滚动行为

#

Android 上,滚动到可滚动区域的边缘会显示 过度滚动辉光指示器(基于当前 Material 主题的颜色)。

iOS 上,滚动到可滚动区域的边缘会 过度滚动,并伴随越来越大的阻力,然后回弹。

Android and iOS scrollables being flung past their edge and exhibiting platform specific overscroll behavior
动态过度滚动比较
Android and iOS scrollables being overscrolled from a resting position and exhibiting platform specific overscroll behavior
静态过度滚动比较

动量

#

iOS 上,在同一方向上重复弹动会叠加动量,并随着每次连续弹动而增加速度。Android 上没有等效的行为。

Repeated scroll flings building momentum on iOS
iOS 滚动动量

返回顶部

#

iOS 上,点击操作系统状态栏会将主滚动控制器滚动到顶部位置。Android 上没有等效的行为。

Tapping the status bar scrolls the primary scrollable back to the top
iOS 状态栏点击顶部

排版

#

使用 Material 包时,排版会自动默认为适合平台的字体系列。Android 使用 Roboto 字体。iOS 使用 San Francisco 字体。

使用 Cupertino 包时,默认主题 使用 San Francisco 字体。

San Francisco 字体的许可证限制其仅用于在 iOS、macOS 或 tvOS 上运行的软件。因此,如果平台在调试时被覆盖到 iOS 或使用默认的 Cupertino 主题,则在 Android 上运行时会使用备用字体。

您可能选择调整 Material 小部件的文本样式以匹配 iOS 上的默认文本样式。您可以在 UI 组件部分 中查看特定于小部件的示例。

Roboto font on Android
Android 上的 Roboto
San Francisco font on iOS
iOS 上的 San Francisco

图标

#

使用 Material 包时,某些图标会根据平台自动显示不同的图形。例如,溢出按钮的三个点在 iOS 上是水平的,在 Android 上是垂直的。后退按钮在 iOS 上是一个简单的 Chevron,在 Android 上有一个茎/轴。

Android appropriate icons
Android 上的图标
iOS appropriate icons
iOS 上的图标

Material 库还通过 Icons.adaptive 提供一组平台自适应图标。

触觉反馈

#

Material 和 Cupertino 包会在某些情况下自动触发平台适用的触觉反馈。

例如,通过文本字段长按选择单词会在 Android 上触发“嗡嗡”振动,而在 iOS 上不会。

在 iOS 上滚动选择器项目会触发“轻微冲击”敲击声,而在 Android 上没有反馈。

文本编辑

#

Material 和 Cupertino 文本输入字段都支持拼写检查,并适应使用平台的正确拼写检查配置以及正确的拼写检查菜单和高亮颜色。

Flutter 在编辑文本字段内容时还会进行以下适配以匹配当前平台。

键盘手势导航

#

Android 上,可以在软键盘的 空格 键上进行水平滑动以在 Material 和 Cupertino 文本字段中移动光标。

在具有 3D Touch 功能的 iOS 设备上,可以在软键盘上进行按压拖动手势以通过浮动光标在 2D 中移动光标。这在 Material 和 Cupertino 文本字段中都有效。

Moving the cursor via the space key on Android
Android 空格键移动光标
Moving the cursor via 3D Touch drag on the keyboard on iOS
iOS 3D Touch 拖动移动光标

文本选择工具栏

#

使用 Android 上的 Material 时,在文本字段中进行文本选择时会显示 Android 样式的选择工具栏。

使用 iOS 上的 Material 或使用 Cupertino 时,在文本字段中进行文本选择时会显示 iOS 样式的选择工具栏。

Android appropriate text toolbar
Android 文本选择工具栏
iOS appropriate text toolbar
iOS 文本选择工具栏

单次点击手势

#

使用 Android 上的 Material 时,在文本字段中点击一次会将光标置于点击位置。

折叠的文本选择还会显示一个可拖动的手柄以随后移动光标。

使用 iOS 上的 Material 或使用 Cupertino 时,在文本字段中点击一次会将光标置于点击单词的最近边缘。

iOS 上的折叠文本选择没有可拖动的手柄。

Moving the cursor to the tapped position on Android
Android 点击
Moving the cursor to the nearest edge of the tapped word on iOS
iOS 点击

长按手势

#

使用 Android 上的 Material 时,长按会选择长按下的单词。释放时会显示选择工具栏。

使用 iOS 上的 Material 或使用 Cupertino 时,长按会将光标置于长按的位置。释放时会显示选择工具栏。

Selecting a word via long press on Android
Android 长按
Selecting a position via long press on iOS
iOS 长按

长按拖动手势

#

使用 Android 上的 Material 时,在长按时拖动会扩展选定的单词。

使用 iOS 上的 Material 或使用 Cupertino 时,在长按时拖动会移动光标。

Expanding word selection via long press drag on Android
Android 长按拖动
Moving the cursor via long press drag on iOS
iOS 长按拖动

双击手势

#

在 Android 和 iOS 上,双击都会选择接收双击的单词并显示选择工具栏。

Selecting a word via double tap on Android
Android 双击
Selecting a word via double tap on iOS
iOS 双击

UI 组件

#

本节包含有关如何调整 Material 小部件以在 iOS 上提供自然且引人入胜的体验的初步建议。欢迎您在 问题 #8427 上提供反馈。

使用 .adaptive() 构造函数的小部件

#

几个小部件支持 .adaptive() 构造函数。下表列出了这些小部件。当应用在 iOS 设备上运行时,自适应构造函数会替换相应的 Cupertino 组件。

下表中的小部件主要用于输入、选择和显示系统信息。由于这些控件与操作系统紧密集成,因此用户已经接受并学会了识别和响应它们。因此,我们建议您遵循平台约定。

Material 小部件Cupertino 小部件自适应构造函数
Switch in Material 3
开关
Switch in HIG
CupertinoSwitch
Switch.adaptive()
Slider in Material 3
滑块
Slider in HIG
CupertinoSlider
Slider.adaptive()
Circular progress indicator in Material 3
圆形进度指示器
Activity indicator in HIG
CupertinoActivityIndicator
CircularProgressIndicator.adaptive()
 Checkbox in Material 3
复选框
Checkbox in HIG
CupertinoCheckbox
Checkbox.adaptive()
Radio in Material 3
单选按钮
Radio in HIG
CupertinoRadio
Radio.adaptive()
AlertDialog in Material 3
警告对话框
AlertDialog in HIG
CupertinoAlertDialog
AlertDialog.adaptive()

顶部应用栏和导航栏

#

自 Android 12 起,顶部应用栏的默认 UI 遵循 Material 3 中定义的设计指南。在 iOS 上,一个名为“导航栏”的等效组件在 Apple 的人机界面指南 (HIG) 中定义。

 Top App Bar in Material 3
Material 3 中的顶部应用栏
Navigation Bar in Human Interface Guidelines
人机界面指南中的导航栏

Flutter 应用中的 AppBar 某些属性应该进行适配,例如系统图标和页面过渡动画。当使用 Material 的 AppBarSliverAppBar 组件时,这些属性会自动适配。您还可以进一步自定义这些组件的属性,使其更好地匹配 iOS 平台的样式,如下所示。

dart
// Map the text theme to iOS styles
TextTheme cupertinoTextTheme = TextTheme(
    headlineMedium: CupertinoThemeData()
        .textTheme
        .navLargeTitleTextStyle
         // fixes a small bug with spacing
        .copyWith(letterSpacing: -1.5),
    titleLarge: CupertinoThemeData().textTheme.navTitleTextStyle)
...

// Use iOS text theme on iOS devices
ThemeData(
      textTheme: Platform.isIOS ? cupertinoTextTheme : null,
      ...
)
...

// Modify AppBar properties
AppBar(
        surfaceTintColor: Platform.isIOS ? Colors.transparent : null,
        shadowColor: Platform.isIOS ? CupertinoColors.darkBackgroundGray : null,
        scrolledUnderElevation: Platform.isIOS ? .1 : null,
        toolbarHeight: Platform.isIOS ? 44 : null,
        ...
      ),

但是,由于 AppBar 与页面中的其他内容一起显示,因此仅建议在适配样式与应用程序其余部分保持一致的情况下进行适配。您可以在 GitHub 上关于 AppBar 适配的讨论 中查看其他代码示例和更详细的解释。

底部导航栏

#

从 Android 12 开始,底部导航栏的默认 UI 遵循 Material 3 中定义的设计指南。在 iOS 上,一个名为“Tab Bars”的等效组件在 Apple 的 Human Interface Guidelines (HIG) 中定义。

Bottom Navigation Bar in Material 3
Material 3 中的底部导航栏
Tab Bar in Human Interface Guidelines
Human Interface Guidelines 中的 Tab Bar

由于 Tab Bar 在整个应用程序中始终存在,因此它们应该与您的品牌保持一致。但是,如果您选择在 Android 上使用 Material 的默认样式,则可以考虑适配 iOS 的默认 Tab Bar。

要实现平台特定的底部导航栏,您可以在 Android 上使用 Flutter 的 NavigationBar 组件,在 iOS 上使用 CupertinoTabBar 组件。下面是一个您可以用来显示平台特定导航栏的代码片段。

dart
final Map<String, Icon> _navigationItems = {
    'Menu': Platform.isIOS ? Icon(CupertinoIcons.house_fill) : Icon(Icons.home),
    'Order': Icon(Icons.adaptive.share),
  };

...

Scaffold(
  body: _currentWidget,
  bottomNavigationBar: Platform.isIOS
          ? CupertinoTabBar(
              currentIndex: _currentIndex,
              onTap: (index) {
                setState(() => _currentIndex = index);
                _loadScreen();
              },
              items: _navigationItems.entries
                  .map<BottomNavigationBarItem>(
                      (entry) => BottomNavigationBarItem(
                            icon: entry.value,
                            label: entry.key,
                          ))
                  .toList(),
            )
          : NavigationBar(
              selectedIndex: _currentIndex,
              onDestinationSelected: (index) {
                setState(() => _currentIndex = index);
                _loadScreen();
              },
              destinations: _navigationItems.entries
                  .map<Widget>((entry) => NavigationDestination(
                        icon: entry.value,
                        label: entry.key,
                      ))
                  .toList(),
            ));

文本字段

#

从 Android 12 开始,文本字段遵循 Material 3 (M3) 设计指南。在 iOS 上,Apple 的 Human Interface Guidelines (HIG) 定义了一个等效组件。

Text Field in Material 3
Material 3 中的文本字段
Text Field in Human Interface Guidelines
HIG 中的文本字段

由于文本字段需要用户输入,
因此其设计应遵循平台约定。

要在 Flutter 中实现平台特定的 TextField,您可以调整 Material TextField 的样式。

dart
Widget _createAdaptiveTextField() {
  final _border = OutlineInputBorder(
    borderSide: BorderSide(color: CupertinoColors.lightBackgroundGray),
  );

  final iOSDecoration = InputDecoration(
    border: _border,
    enabledBorder: _border,
    focusedBorder: _border,
    filled: true,
    fillColor: CupertinoColors.white,
    hoverColor: CupertinoColors.white,
    contentPadding: EdgeInsets.fromLTRB(10, 0, 0, 0),
  );

  return Platform.isIOS
      ? SizedBox(
          height: 36.0,
          child: TextField(
            decoration: iOSDecoration,
          ),
        )
      : TextField();
}

要了解有关适配文本字段的更多信息,请查看 GitHub 上关于文本字段的讨论。您可以在讨论中留下反馈或提出问题。