自动平台适配
了解更多关于 Flutter 平台自适应性的信息。
适配理念
#通常,平台自适应性存在两种情况
- 属于操作系统环境行为的事物(例如文本编辑和滚动),如果采用不同的行为则会被视为“错误”。
- 传统上使用 OEM SDK 在应用中实现的事物(例如在 iOS 上使用平行标签页,或在 Android 上显示
android.app.AlertDialog)。
本文主要涵盖 Flutter 在 Android 和 iOS 上针对第 1 种情况提供的自动适配功能。
对于第 2 种情况,Flutter 捆绑了实现相应平台约定效果的方法,但在需要应用设计选择时不会自动适配。相关讨论,请参阅 issue #8410 以及 Material/Cupertino 自适应组件问题定义。
关于在 Android 和 iOS 上使用不同信息架构结构但共享相同内容代码的应用示例,请参阅 platform_design 代码示例。
页面导航
#Flutter 提供了 Android 和 iOS 上常见的导航模式,并会自动根据当前平台调整导航动画。
导航转场
#在 Android 上,默认的 Navigator.push() 转场效仿了 startActivity(),它通常具有一种自下而上的动画变体。
在 iOS 上
- 默认的
Navigator.push()API 会产生一种 iOS 显示/推送样式转场,它根据区域设置的 RTL 设置从结束到开始方向进行动画处理。新路由背后的页面也会像在 iOS 中一样向相同方向视差滑动。 - 当推送一个
PageRoute.fullscreenDialog为 true 的页面路由时,存在一种单独的自下而上的转场样式。这代表了 iOS 的呈现/模态样式转场,通常用于全屏模态页面。

Android 页面转场

iOS 推送转场

iOS 呈现转场
平台特定的转场详情
#在 Android 上,Flutter 使用 ZoomPageTransitionsBuilder 动画。当用户点击项目时,UI 会缩放到包含该项目的屏幕。当用户点击返回时,UI 会缩回上一个屏幕。
在 iOS 上使用推送样式转场时,Flutter 捆绑的 CupertinoNavigationBar 和 CupertinoSliverNavigationBar 导航栏会自动将每个子组件动画过渡到下一页或上一页对应的 CupertinoNavigationBar 或 CupertinoSliverNavigationBar 组件。

Android

iOS 导航栏
返回导航
#在 Android 上,默认情况下,操作系统返回按钮会发送到 Flutter 并弹出 WidgetsApp 的 Navigator 顶层路由。
在 iOS 上,边缘滑动的手势可用于弹出顶层路由。

Android 返回按钮

iOS 返回滑动操作
滚动
#滚动是平台视觉和交互体验的重要组成部分,Flutter 会自动调整滚动行为以匹配当前平台。
物理模拟
#Android 和 iOS 都有复杂的滚动物理模拟,很难用语言描述。通常,iOS 的可滚动对象具有更大的重量和动态摩擦力,但 Android 具有更大的静态摩擦力。因此,iOS 在低速时获得高速度的过程更缓慢,但停止时更平滑,且在低速时更“滑”。

轻微抛动对比

中等抛动对比

强烈抛动对比
过度滚动行为
#在 Android 上,滚动超过可滚动区域边缘时会显示一个 过度滚动发光指示器(基于当前 Material 主题的颜色)。
在 iOS 上,滚动超过可滚动区域边缘时会以增加的阻力进行 过度滚动,然后弹回。

动态过度滚动对比

静态过度滚动对比
滚动条
#在 基于 Material 的平台(如 Android 和 Web)上,滚动条通常在滚动时可见,并可能根据平台和主题保持可见。
在 基于 Cupertino 的平台(如 iOS)上,滚动条更为极简,通常仅在用户主动滚动时短暂出现,并在交互停止时淡出。
这种差异反映了每个平台的视觉约定,有助于在不同设备上保持原生的视觉和交互体验。
动量
#在 iOS 上,在同一方向重复抛动会叠加动量,并随着每次连续抛动产生更大的速度。Android 上没有对应的行为。

iOS 滚动动量
返回顶部
#在 iOS 上,点击操作系统状态栏会将主要滚动控制器滚动到顶部位置。Android 上没有对应的行为。

iOS 点击状态栏返回顶部
排版
#使用 Material 包时,排版会自动默认使用适合该平台的字体系列。Android 使用 Roboto 字体。iOS 使用 San Francisco 字体。
使用 Cupertino 包时,默认主题使用 San Francisco 字体。
San Francisco 字体的许可协议限制其仅在 iOS、macOS 或 tvOS 上运行的软件中使用。因此,如果平台被调试覆盖为 iOS 或使用默认的 Cupertino 主题,在 Android 上运行时将使用备用字体。
您可以选择调整 Material 组件的文本样式,以匹配 iOS 上的默认文本样式。您可以在 UI 组件部分中查看针对具体组件的示例。

Android 上的 Roboto

iOS 上的 San Francisco
图标设计
#使用 Material 包时,某些图标会自动根据平台显示不同的图形。例如,溢出菜单按钮的三个点在 iOS 上是水平排列的,而在 Android 上则是垂直排列的。返回按钮在 iOS 上是一个简单的 V 形符号(chevron),而在 Android 上则带有一个杆/轴。

Android 上的图标

iOS 上的图标
Material 库还通过 Icons.adaptive 提供了一组平台自适应图标。
触觉反馈
#Material 和 Cupertino 包在特定场景下会自动触发适合平台的触觉反馈。
例如,通过长按文本字段选择单词会触发 Android 上的“蜂鸣”振动,而 iOS 上则不会。
在 iOS 上滚动选择器项目会触发“轻微撞击”反馈,而 Android 上没有反馈。
文本编辑
#Material 和 Cupertino 文本输入字段均支持拼写检查,并会适配使用适合该平台的拼写检查配置,以及适当的拼写检查菜单和高亮颜色。
Flutter 在编辑文本字段内容时也会进行以下调整,以匹配当前平台。
键盘手势导航
#在 Android 上,可以在软键盘的 空格 键上进行水平滑动,以在 Material 和 Cupertino 文本字段中移动光标。
在具有 3D Touch 功能的 iOS 设备上,可以在软键盘上执行强力按压拖动手势,通过浮动光标在二维空间移动光标。这适用于 Material 和 Cupertino 文本字段。

Android 空格键光标移动

iOS 3D Touch 拖动光标移动
文本选择工具栏
#在 Android 上的 Material 中,当在文本字段中进行文本选择时,会显示 Android 样式的选择工具栏。
在 iOS 上的 Material 或使用 Cupertino 时,当在文本字段中进行文本选择时,会显示 iOS 样式的选择工具栏。

Android 文本选择工具栏

iOS 文本选择工具栏
单点触控手势
#在 Android 上的 Material 中,在文本字段中单次点击会将光标置于点击位置。
折叠的文本选择也会显示一个可拖动的句柄,随后可以用来移动光标。
在 iOS 上的 Material 或使用 Cupertino 时,在文本字段中单次点击会将光标置于所点击单词的最接近边缘处。
在 iOS 上,折叠的文本选择没有可拖动的句柄。

Android 点击

iOS 点击
长按手势
#在 Android 上的 Material 中,长按会选择长按位置下的单词。松开后显示选择工具栏。
在 iOS 上的 Material 或使用 Cupertino 时,长按会将光标置于长按位置。松开后显示选择工具栏。

Android 长按

iOS 长按
长按拖动手势
#在 Android 上的 Material 中,按住长按的同时进行拖动会扩展选中的单词。
在 iOS 上的 Material 或使用 Cupertino 时,按住长按的同时进行拖动会移动光标。

Android 长按拖动

iOS 长按拖动
双击手势
#在 Android 和 iOS 上,双击会选择被双击的单词并显示选择工具栏。

Android 双击

iOS 双击
UI 组件
#本节包含关于如何适配 Material 组件以在 iOS 上提供自然且令人信服的体验的初步建议。欢迎您在 issue #8427 提供反馈。
带有 .adaptive() 构造函数的组件
#一些组件支持 .adaptive() 构造函数。下表列出了这些组件。当应用在 iOS 设备上运行时,自适应构造函数会替换为相应的 Cupertino 组件。
下表中的组件主要用于输入、选择和显示系统信息。由于这些控件与操作系统紧密集成,用户已经习惯于识别和响应它们。因此,我们建议您遵循平台约定。
| Material 组件 | Cupertino 组件 | 自适应构造函数 |
|---|---|---|
Switch
|
CupertinoSwitch
|
Switch.adaptive()
|
Slider
|
CupertinoSlider
|
Slider.adaptive()
|
CircularProgressIndicator
|
CupertinoActivityIndicator
|
CircularProgressIndicator.adaptive()
|
RefreshProgressIndicator
|
CupertinoActivityIndicator
|
RefreshIndicator.adaptive()
|
Checkbox
|
CupertinoCheckbox
|
Checkbox.adaptive()
|
Radio
|
CupertinoRadio
|
Radio.adaptive()
|
AlertDialog
|
CupertinoAlertDialog
|
AlertDialog.adaptive()
|
顶部应用栏和导航栏
#自 Android 12 起,顶部应用栏的默认 UI 遵循 Material 3 中定义的指南。在 iOS 上,Apple 的人机界面指南 (HIG) 中定义了一个名为“导航栏”的等效组件。

Material 3 中的顶部应用栏

人机界面指南中的导航栏
Flutter 应用中应用栏的某些属性应进行调整,例如系统图标和页面转场。当使用 Material 的 AppBar 和 SliverAppBar 组件时,这些属性已经自动适配。您还可以进一步自定义这些组件的属性,以更好地匹配 iOS 平台风格,如下所示。
// 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,
...
),
但是,由于应用栏与页面中的其他内容一起显示,建议仅在与应用其余部分保持一致的情况下调整样式。您可以在 GitHub 关于应用栏适配的讨论中查看更多代码示例和详细说明。
底部导航栏
#自 Android 12 起,底部导航栏的默认 UI 遵循 Material 3 中定义的指南。在 iOS 上,Apple 的人机界面指南 (HIG) 中定义了一个名为“标签栏”的等效组件。

Material 3 中的底部导航栏

人机界面指南中的标签栏
由于标签栏在整个应用中都是持久存在的,它们应该符合您自己的品牌风格。但是,如果您选择在 Android 上使用 Material 的默认样式,您可以考虑适配默认的 iOS 标签栏。
要实现平台特定的底部导航栏,您可以在 Android 上使用 Flutter 的 NavigationBar 组件,在 iOS 上使用 CupertinoTabBar 组件。下面是一个代码片段,您可以对其进行调整以显示平台特定的导航栏。
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 的 人机界面指南 (HIG) 定义了一个等效组件。

Material 3 中的文本字段

HIG 中的文本字段
由于文本字段需要用户输入,其设计应遵循平台约定。
要在 Flutter 中实现特定于平台的 TextField,您可以调整 Material TextField 的样式。
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 关于文本字段的讨论。您可以在讨论中留下反馈或提出问题。