不同平台操作体验的差异和适配

适配哲学

Adaptation philosophy

平台适配通常有两种情形:

There are generally two cases of platform adaptiveness:

  1. 操作系统所特有的操作体验(例如文本编辑和滚动)。如果操作体验与操作系统不一致,则通常会被认为是“错误的”。

    Things that are behaviors of the OS environment (such as text editing and scrolling) and that would be ‘wrong’ if a different behavior took place.

  2. 使用 OEM 提供的 SDK 实现的功能体验(例如 iOS 常使用的选项卡, Android 使用 android.app.AlertDialog 显示一个提示窗口)。

    Things that are conventionally implemented in apps using the OEM’s SDKs (such as using parallel tabs on iOS or showing an android.app.AlertDialog on Android).

本文囊括了 Flutter 为解决情形 1 而提供的覆盖 Android 和 iOS 的自动适配。

This article mainly covers the automatic adaptations provided by Flutter in case 1 on Android and iOS.

对于情形 2,Flutter 提供了一些工具可以生成符合平台习惯的体验,但是不会根据平台自动适配,需要根据 App 设计来手工选择。更多有关的讨论,请访问 issue #8410 和这个文档 定义 Material/Cupertino widget 适配问题

For case 2, Flutter bundles the means to produce the appropriate effects of the platform conventions but doesn’t adapt automatically when app design choices are needed. For a discussion, see issue #8410 and the Material/Cupertino adaptive widget problem definition.

如果一个应用需要在 Android 和 iOS 不同架构上使用相同的代码,请参阅 platform_design 这份代码示例

For an example of an app using different information architecture structures on Android and iOS but sharing the same content code, see the platform_design code samples.

Page navigation

Flutter 分别为 Android 和 iOS 提供了各自平台的导航模式,并根据当前平台自动适配导航转场动画。

Flutter provides the navigation patterns seen on Android and iOS and also automatically adapts the navigation animation to the current platform.

Navigation transitions

Android 平台,默认提供的 Navigator.push() 转场动画模仿了 startActivity() 的动画,即一种自下而上的动画效果。

On Android, the default Navigator.push() transition is modeled after startActivity()), which generally has one bottom-up animation variant.

iOS 平台:

On iOS:

  • iOS 的 Navigator.push() API 提供了 iOS 上的 Show 转场动画(也被成为 Push 转场动画),即根据语言的方向设置,执行一种从后到前的滚动动画效果。在显示新页面的时候,原来的页面也会沿着相同的方向进行视差滚动。

    The default Navigator.push() API produces an iOS Show/Push style transition that animates from end-to-start depending on the locale’s RTL setting. The page behind the new route also parallax-slides in the same direction as in iOS.

  • 当显示一个页面,且 PageRoute.fullscreenDialog 是 true 的时候, iOS 提供了另外一种自下而上的动画效果。这个动画通常被用在展示全屏模态页,也被成为 iOS 上的 Present 转场动画或 Modal 转场动画。

    A separate bottom-up transition style exists when pushing a page route where PageRoute.fullscreenDialog is true. This represents iOS’s Present/Modal style transition and is typically used on fullscreen modal pages.

An animation of the bottom-up page transition on Android
Android page transitionAndroid 转场动画
An animation of the end-start style push page transition on iOS
iOS push transitioniOS Push 转场动画
An animation of the bottom-up style present page transition on iOS
iOS present transitioniOS Present 转场动画

不同平台的转场动画细节

Platform-specific transition details

Android 平台上,根据你的操作系统版本差异,有两种不通的转场动画:

On Android, two page transition animation styles exist depending on your OS version:

当在 iOS 平台上使用 Push 转场特效的时候, Flutter 内置的 CupertinoNavigationBarCupertinoSliverNavigationBar 会自动的给当前页下一页的子组件使用正确的动画效果(CupertinoNavigationBar 或者 CupertinoSliverNavigationBar)。

On iOS when the push style transition is used, Flutter’s bundled CupertinoNavigationBar and CupertinoSliverNavigationBar nav bars automatically animate each subcomponent to its corresponding subcomponent on the next or previous page’s CupertinoNavigationBar or CupertinoSliverNavigationBar.

An animation of the page transition on Android pre-Android P
Android Pre-PAndroid P 以前
An animation of the page transition on Android on Android P
Android Post-PAndroid P 以后
An animation of the nav bar transitions during a page transition on iOS
iOS Nav BariOS 导航栏

返回导航

Back navigation

Android 平台,通常操作系统的返回按钮触发的事件会发给 Flutter,并弹出 WidgetsApp 路由的最顶端。

On Android, the OS back button, by default, is sent to Flutter and pops the top route of the WidgetsApp’s Navigator.

iOS 平台,从屏幕边缘的轻扫手势会弹出路由的最顶端。

On iOS, an edge swipe gesture can be used to pop the top route.

A page transition triggered by the Android back button
Android back buttonAndroid 返回按钮
A page transition triggered by an iOS back swipe gesture
iOS back swipe gestureiOS 轻扫返回手势

滚动

Scrolling

滚动是不通平台提供独有体验非常重要的一环,Flutter 会根据当前的平台自动适配滚动体验。

Scrolling is an important part of the platform’s look and feel, and Flutter automatically adjusts the scrolling behavior to match the current platform.

物理仿真

Physics simulation

Android 和 iOS 平台都提供了非常复杂的滚动物理仿真,因而很难用语言来描述。通常来说,iOS 的滚动通常提供更多的分量和动态的阻力;而 Android 则更多的使用静态的阻力。所以,iOS 随着滚动慢慢的达到高速,且不会突然的停止,而且在慢速的时候显得更顺滑。

Android and iOS both have complex scrolling physics simulations that are difficult to describe verbally. Generally, iOS’s scrollable has more weight and dynamic friction but Android has more static friction. Therefore iOS gains high speed more gradually but stops less abruptly and is more slippery at slow speeds.

A soft fling where the iOS scrollable slid longer at lower speed than Android
Soft fling comparison突然慢慢滚动的效果比较
A medium force fling where the Android scrollable reached speed faster and stopped more abruptly after reaching a longer distance
Medium fling comparison突然较快的滚动效果比较
A strong fling where the Android scrollable reach speed faster and reached significantly more distance
Strong fling comparison突然强烈的滚动效果比较

滚动边界行为

Overscroll behavior

Android 平台,滚动达到边界的时候,会显示 滚动灰色指示 (具体颜色根据 Material 主题而有所不同)。

On Android, scrolling past the edge of a scrollable shows an overscroll glow indicator (based on the color of the current Material theme).

iOS 平台,滚动达到边界的时候,会显示一个 滚动边界 的弹簧效果。

On iOS, scrolling past the edge of a scrollable overscrolls with increasing resistance and snaps back.

Android and iOS scrollables being flung past their edge and exhibiting platform specific overscroll behavior
Dynamic overscroll comparison动态滚动边界效果比较
Android and iOS scrollables being overscrolled from a resting position and exhibiting platform specific overscroll behavior
Static overscroll comparison静态滚动边界效果比较

动量

Momentum

iOS 平台,不停的按相同方向滚动会产生动量叠加,从而连续滚动速度会越来越快。在 Android 平台上没有对应的行为。

On iOS, repeated flings in the same direction stacks momentum and builds more speed with each successive fling. There is no equivalent behavior on Android.

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

返回顶部

Return to top

iOS 平台,点击操作系统的状态栏,主要的滚动条控制器会滚动到顶部。 Android 没有对应的行为。

On iOS, tapping the OS status bar scrolls the primary scroll controller to the top position. There is no equivalent behavior on Android.

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

排版

Typography

当使用 Material package 的时候,排版会根据平台自动使用对应的字体。 Android 平台会使用 Roboto 字体,而 iOS 则会使用系统自带的 San Francisco 字体。

When using the Material package, the typography automatically defaults to the font family appropriate for the platform. On Android, the Roboto font is used. On iOS, the OS’s San Francisco font family is used.

当使用 Cupertino 包的时候,默认主题 会一直使用 San Francisco 字体。

When using the Cupertino package, the default theme always uses the San Francisco font.

San Francisco 字体的授权限制了它只能被用在运行于 iOS、macOS 和 tvOS 平台上的软件。因此当运行在 Android 平台的时候,即使强制覆盖系统平台为 iOS 或者使用 Cupertino 默认主题,都会使用对应的替代字体。

The San Francisco font license limits its usage to software running on iOS, macOS, or tvOS only. Therefore a fallback font is used when running on Android if the platform is debug-overridden to iOS or the default Cupertino theme is used.

Roboto font on Android
Roboto on AndroidAndroid 平台 Robot 字体
San Francisco font on iOS
San Francisco on iOSiOS 平台 San Francisco 字体

图标

Iconography

当使用 Material 包的时候,根据平台不通,图标的具体样式会有差别。举例来说,更多按钮的图标,Android 上是竖直的三个点而 iOS 是横着的三个点;退回按钮,iOS 是一个简单的 V 型标记,而 Android 平台,V 型标记有个短横线。

When using the Material package, certain icons automatically show different graphics depending on the platform. For instance, the overflow button’s three dots are horizontal on iOS and vertical on Android. The back button is a simple chevron on iOS and has a stem/shaft on Android.

Android appropriate icons
Icons on AndroidAndroid 平台图标
iOS appropriate icons
Icons on iOSiOS 平台图标

触摸反馈

Haptic feedback

Material 和 Cupertino 包在特定场景下都会自动触发符合平台特点的触摸反馈。

The Material and Cupertino packages automatically trigger the platform appropriate haptic feedback in certain scenarios.

例如,在文本输入框控件里面长按选中单词会在 Android 设备上会触发震动,而 iOS 不会。

For instance, a word selection via text field long-press triggers a ‘buzz’ vibrate on Android and not on iOS.

在 iOS 滚动选择器项目列表,会触发一个很轻的敲击音效,而 Android 则不会。

Scrolling through picker items on iOS triggers a ‘light impact’ knock and no feedback on Android.

文本编辑

Text editing

Flutter 会根据当前平台来适配正确的文本编辑体验。

Flutter also makes the below adaptations while editing the content of text fields to match the current platform.

键盘手势导航

Keyboard gesture navigation

Android 平台,在虚拟键盘空格键上可以通过左右轻扫来移动光标, Material 和 Cupertino 的文本输入框控件都支持该特性。

On Android, horizontal swipes can be made on the soft keyboard’s spacebar to move the cursor in Material and Cupertino text fields.

iOS 设备提供了 3D Touch 兼容,通过在虚拟键盘上使用长按并拖拽手势可以任意方向移动光标。 Material 和 Cupertino 都对这个功能提供了支持。

On iOS devices with 3D Touch capabilities, a force-press-drag gesture could be made on the soft keyboard to move the cursor in 2D via a floating cursor. This works on both Material and Cupertino text fields.

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

文本选中工具栏

Text selection toolbar

Android 平台上使用 Material,在文本输入框里面选中文本会显示一个 Android 风格的文本选中工具栏。

With Material on Android, the Android style selection toolbar is shown when a text selection is made in a text field.

iOS 平台上使用 Material 或者在两个平台上都使用 Cupertino,在文本输入框里面选中文本会展示一个 iOS 风格的文本选中工具栏。

With Material on iOS or when using Cupertino, the iOS style selection toolbar is shown when a text selection is made in a text field.

Android appropriate text toolbar
Android text selection toolbarAndroid 文本选中工具栏
iOS appropriate text toolbar
iOS text selection toolbariOS 文本选中工具栏

点击手势

Single tap gesture

Android 平台使用 Material,在文本控件中点击会移动光标到点击处。

With Material on Android, a single tap in a text field puts the cursor at the location of the tap.

同时,光标会有一个可移动的把手,随后可以通过这个把手移动光标。

A collapsed text selection also shows a draggable handle to subsequently move the cursor.

iOS 平台使用 Material 或者在两个平台都使用 Cupertino,在文本空间中点击,会把光标移动到点击处最近的单词末尾。

With Material on iOS or when using Cupertino, a single tap in a text field puts the cursor at the nearest edge of the word tapped.

在 iOS 平台上,光标是没有把手的。

Collapsed text selections don’t have draggable handles on iOS.

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

长按手势

Long-press gesture

Android 平台使用 Material,在单词上长按会选中单词,并在释放长按的时候显示文本选中工具栏。

With Material on Android, a long press selects the word under the long press. The selection toolbar is shown upon release.

iOS 平台使用 Material 或者在两个平台都使用 Cupertino,长按会把光标放置到长按的位置,并在释放长按的时候显示文本选中工具栏。

With Material on iOS or when using Cupertino, a long press places the cursor at the location of the long press. The selection toolbar is shown upon release.

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

长按并拖放手势

Long-press drag gesture

Android 平台上使用 Material,长按并拖拽会选中更多单词。

With Material on Android, dragging while holding the long press expands the words selected.

iOS 平台使用 Material 或者在两个平台都使用 Cupertino,长按并拖拽会移动光标。

With Material on iOS or when using Cupertino, dragging while holding the long press moves the cursor.

Expanding word selection via long press drag on Android
Android long press dragAndroid 长按并拖放
Moving the cursor via long press drag on iOS
iOS long press dragiOS 长按并拖放

双击手势

Double tap gesture

Android 和 iOS 平台上,双击选中一个单词都会收到双击手势事件,并显示文本选中工具栏。

On both Android and iOS, a double tap selects the word receiving the double tap and shows the selection toolbar.

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