自动平台适配
适配理念
#通常,平台自适应性存在两种情况
- 操作系统环境的行为(例如文本编辑和滚动),如果发生不同的行为,则会被认为是“错误的”。
- 使用 OEM 的 SDK 在应用中传统实现的功能(例如在 iOS 上使用并行选项卡或在 Android 上显示
android.app.AlertDialog
)。
本文主要介绍 Flutter 在 Android 和 iOS 上针对情况 1 提供的自动适配。
对于情况 2,Flutter 提供了产生平台约定适当效果的方法,但在需要应用设计选择时不会自动适配。有关讨论,请参阅 问题 #8410 和 Material/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 样式过渡,通常用于全屏模态页面。
特定于平台的过渡细节
#在 Android 上,Flutter 使用 ZoomPageTransitionsBuilder
动画。当用户点击某个项目时,UI 会放大到显示该项目的屏幕。当用户点击返回时,UI 会缩小到上一个屏幕。
在使用推送样式过渡的 iOS 上,Flutter 的捆绑 CupertinoNavigationBar
和 CupertinoSliverNavigationBar
导航栏会自动将每个子组件动画化到下一页或上一页的 CupertinoNavigationBar
或 CupertinoSliverNavigationBar
上的对应子组件。
返回导航
#在 Android 上,操作系统后退按钮默认情况下会发送到 Flutter 并弹出 WidgetsApp
的 Navigator 的顶部路由。
在 iOS 上,可以使用边缘滑动手势弹出顶部路由。
滚动
#滚动是平台外观和感觉的重要组成部分,Flutter 会自动调整滚动行为以匹配当前平台。
物理模拟
#Android 和 iOS 都具有复杂的滚动物理模拟,难以用语言描述。通常,iOS 的可滚动内容具有更大的重量和动态摩擦力,但 Android 具有更大的静态摩擦力。因此,iOS 的速度逐渐提高,但停止时不会突然中断,并且在低速时更顺滑。
过度滚动行为
#在 Android 上,滚动到可滚动区域的边缘会显示 过度滚动辉光指示器(基于当前 Material 主题的颜色)。
在 iOS 上,滚动到可滚动区域的边缘会 过度滚动,并伴随越来越大的阻力,然后回弹。
动量
#在 iOS 上,在同一方向上重复弹动会叠加动量,并随着每次连续弹动而增加速度。Android 上没有等效的行为。
返回顶部
#在 iOS 上,点击操作系统状态栏会将主滚动控制器滚动到顶部位置。Android 上没有等效的行为。
排版
#使用 Material 包时,排版会自动默认为适合平台的字体系列。Android 使用 Roboto 字体。iOS 使用 San Francisco 字体。
使用 Cupertino 包时,默认主题 使用 San Francisco 字体。
San Francisco 字体的许可证限制其仅用于在 iOS、macOS 或 tvOS 上运行的软件。因此,如果平台在调试时被覆盖到 iOS 或使用默认的 Cupertino 主题,则在 Android 上运行时会使用备用字体。
您可能选择调整 Material 小部件的文本样式以匹配 iOS 上的默认文本样式。您可以在 UI 组件部分 中查看特定于小部件的示例。
图标
#使用 Material 包时,某些图标会根据平台自动显示不同的图形。例如,溢出按钮的三个点在 iOS 上是水平的,在 Android 上是垂直的。后退按钮在 iOS 上是一个简单的 Chevron,在 Android 上有一个茎/轴。
Material 库还通过 Icons.adaptive
提供一组平台自适应图标。
触觉反馈
#Material 和 Cupertino 包会在某些情况下自动触发平台适用的触觉反馈。
例如,通过文本字段长按选择单词会在 Android 上触发“嗡嗡”振动,而在 iOS 上不会。
在 iOS 上滚动选择器项目会触发“轻微冲击”敲击声,而在 Android 上没有反馈。
文本编辑
#Material 和 Cupertino 文本输入字段都支持拼写检查,并适应使用平台的正确拼写检查配置以及正确的拼写检查菜单和高亮颜色。
Flutter 在编辑文本字段内容时还会进行以下适配以匹配当前平台。
键盘手势导航
#在 Android 上,可以在软键盘的 空格 键上进行水平滑动以在 Material 和 Cupertino 文本字段中移动光标。
在具有 3D Touch 功能的 iOS 设备上,可以在软键盘上进行按压拖动手势以通过浮动光标在 2D 中移动光标。这在 Material 和 Cupertino 文本字段中都有效。
文本选择工具栏
#使用 Android 上的 Material 时,在文本字段中进行文本选择时会显示 Android 样式的选择工具栏。
使用 iOS 上的 Material 或使用 Cupertino 时,在文本字段中进行文本选择时会显示 iOS 样式的选择工具栏。
单次点击手势
#使用 Android 上的 Material 时,在文本字段中点击一次会将光标置于点击位置。
折叠的文本选择还会显示一个可拖动的手柄以随后移动光标。
使用 iOS 上的 Material 或使用 Cupertino 时,在文本字段中点击一次会将光标置于点击单词的最近边缘。
iOS 上的折叠文本选择没有可拖动的手柄。
长按手势
#使用 Android 上的 Material 时,长按会选择长按下的单词。释放时会显示选择工具栏。
使用 iOS 上的 Material 或使用 Cupertino 时,长按会将光标置于长按的位置。释放时会显示选择工具栏。
长按拖动手势
#使用 Android 上的 Material 时,在长按时拖动会扩展选定的单词。
使用 iOS 上的 Material 或使用 Cupertino 时,在长按时拖动会移动光标。
双击手势
#在 Android 和 iOS 上,双击都会选择接收双击的单词并显示选择工具栏。
UI 组件
#本节包含有关如何调整 Material 小部件以在 iOS 上提供自然且引人入胜的体验的初步建议。欢迎您在 问题 #8427 上提供反馈。
使用 .adaptive() 构造函数的小部件
#几个小部件支持 .adaptive()
构造函数。下表列出了这些小部件。当应用在 iOS 设备上运行时,自适应构造函数会替换相应的 Cupertino 组件。
下表中的小部件主要用于输入、选择和显示系统信息。由于这些控件与操作系统紧密集成,因此用户已经接受并学会了识别和响应它们。因此,我们建议您遵循平台约定。
Material 小部件 | Cupertino 小部件 | 自适应构造函数 |
---|---|---|
开关 | CupertinoSwitch | Switch.adaptive() |
滑块 | CupertinoSlider | Slider.adaptive() |
圆形进度指示器 | CupertinoActivityIndicator | CircularProgressIndicator.adaptive() |
复选框 | CupertinoCheckbox | Checkbox.adaptive() |
单选按钮 | CupertinoRadio | Radio.adaptive() |
警告对话框 | CupertinoAlertDialog | AlertDialog.adaptive() |
顶部应用栏和导航栏
#自 Android 12 起,顶部应用栏的默认 UI 遵循 Material 3 中定义的设计指南。在 iOS 上,一个名为“导航栏”的等效组件在 Apple 的人机界面指南 (HIG) 中定义。
Flutter 应用中的 AppBar 某些属性应该进行适配,例如系统图标和页面过渡动画。当使用 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,
...
),
但是,由于 AppBar 与页面中的其他内容一起显示,因此仅建议在适配样式与应用程序其余部分保持一致的情况下进行适配。您可以在 GitHub 上关于 AppBar 适配的讨论 中查看其他代码示例和更详细的解释。
底部导航栏
#从 Android 12 开始,底部导航栏的默认 UI 遵循 Material 3 中定义的设计指南。在 iOS 上,一个名为“Tab Bars”的等效组件在 Apple 的 Human Interface Guidelines (HIG) 中定义。
由于 Tab Bar 在整个应用程序中始终存在,因此它们应该与您的品牌保持一致。但是,如果您选择在 Android 上使用 Material 的默认样式,则可以考虑适配 iOS 的默认 Tab Bar。
要实现平台特定的底部导航栏,您可以在 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 的 Human Interface Guidelines (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 上关于文本字段的讨论。您可以在讨论中留下反馈或提出问题。
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-07-30。 查看源代码 或 报告问题.