自动平台改编
自适应理念
一般来说,平台自适应性有两种情况
- 操作系统环境的行为(例如文本编辑和滚动),如果采用不同的行为,则会“出错”。
- 使用 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显示/推送样式过渡,该过渡根据区域设置的RTL设置从末尾到开头进行动画。新路由后面的页面也像在iOS中一样向同一方向进行视差滑动。 - 在推送页面路由时存在一个单独的自底向上过渡样式,其中
PageRoute.fullscreenDialog
为真。这表示iOS的显示/模态样式过渡,通常用于全屏模态页面。
特定于平台的过渡详情
在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 上是一个简单的 V 形,在 Android 上有一个茎/轴。
Material 库还通过 Icons.adaptive
提供了一组平台自适应图标。
触觉反馈
Material 和 Cupertino 软件包会在某些情况下自动触发适合平台的触觉反馈。
例如,通过文本字段长按选择单词会在 Android 上触发“嗡嗡”振动,而在 iOS 上不会。
在 iOS 上滚动选择器项目会触发“轻微冲击”敲击,而在 Android 上没有反馈。
文本编辑
Flutter 还会在编辑文本字段内容时进行以下调整以匹配当前平台。
键盘手势导航
在 Android 上,可以在软键盘的 space 键上进行水平滑动以在 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 小组件 | 自适应构造函数 |
---|---|---|
Switch
|
CupertinoSwitch
|
Switch.adaptive() |
Slider
|
CupertinoSlider
|
Slider.adaptive() |
CircularProgressIndicator
|
CupertinoActivityIndicator
|
CircularProgressIndicator.adaptive() |
Checkbox
|
CupertinoCheckbox
|
Checkbox.adaptive() |
Radio
|
CupertinoRadio
|
Radio.adaptive() |
顶部应用栏和导航栏
自 Android 12 起,顶部应用栏的默认 UI 遵循 Material 3 中定义的设计指南。在 iOS 上,Apple 的 人机界面指南 (HIG) 中定义了一个名为“导航栏”的等效组件。
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) 中定义了一个名为“标签栏”的等效组件。
由于选项卡栏在您的应用中是持久的,因此它们应与您自己的品牌相匹配。但是,如果您选择在 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) 定义了一个等效的组件。
由于文本字段需要用户输入,
因此它们的设计应遵循平台约定。
要在 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 讨论。您可以在讨论中留下反馈或提出问题。
警报对话框
自 Android 12 起,警报对话框的默认 UI(也称为“基本对话框”)遵循 Material 3 (M3) 中定义的设计准则。在 iOS 上,Apple 的 人机界面指南 (HIG) 中定义了一个称为“警报”的等效组件。
由于警报对话框通常与操作系统紧密集成,因此它们的设计通常需要遵循平台约定。当对话框用于请求用户输入有关安全、隐私和破坏性操作(例如永久删除文件)的信息时,这一点尤其重要。作为例外,可以在非关键用户流程中使用品牌警报对话框设计来突出显示特定信息或消息。
要实现特定于平台的警报对话框,您可以在 Android 上使用 Flutter 的 AlertDialog
窗口小部件,在 iOS 上使用 CupertinoAlertDialog
窗口小部件。以下是您可以改编以显示特定于平台的警报对话框的代码片段。
void _showAdaptiveDialog(
context, {
required Text title,
required Text content,
required List<Widget> actions,
}) {
Platform.isIOS || Platform.isMacOS
? showCupertinoDialog<String>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: title,
content: content,
actions: actions,
),
)
: showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: title,
content: content,
actions: actions,
),
);
}
要了解有关改编警报对话框的更多信息,请查看 关于对话框改编的 GitHub 讨论。您可以在讨论中留下反馈或提出问题。