自動平臺適配
瞭解更多關於 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 Show/Push 風格的過渡,該過渡根據區域設定的 RTL 設定從右向左或從左向右進行動畫處理。新路線後面的頁面也以與 iOS 中相同的方式進行視差滑動。 - 當推送頁面路由且
PageRoute.fullscreenDialog為 true 時,存在單獨的自底向上的過渡風格。這代表 iOS 的 Present/Modal 風格過渡,通常用於全屏模態頁面。

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 更平穩地獲得高速,但停止得不那麼突然,並且在低速時更具滑動性。

軟 fling 比較

中 fling 比較

強 fling 比較
超滾動行為
#在 Android 上,滾動超出可滾動物件的邊緣會顯示一個 超滾動發光指示器(基於當前 Material 主題的顏色)。
在 iOS 上,滾動超出可滾動物件的邊緣會以越來越大的阻力進行 超滾動 並回彈。

動態超滾動比較

靜態超滾動比較
捲軸
#在 基於 Material 的平臺(例如 Android 和 Web)上,捲軸通常在滾動期間可見,並且可能根據平臺和主題保持可見。
在 基於 Cupertino 的平臺(例如 iOS)上,捲軸更加簡潔,通常僅在使用者主動滾動時短暫出現,並在互動停止時消失。
這種差異反映了每個平臺的視覺約定,有助於在裝置上保持原生外觀和感覺。
動量
#在 iOS 上,同一方向上的重複 fling 會堆疊動量並隨著每次後續 fling 增加速度。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 上是一個簡單的向左箭頭,在 Android 上有一個莖/軸。

Android 上的圖示

iOS 上的圖示
Material 庫還透過 Icons.adaptive 提供一組平臺自適應圖示。
觸覺反饋
#Material 和 Cupertino 包會自動觸發在某些情況下平臺適當的觸覺反饋。
例如,透過文字欄位長按進行的單詞選擇會在 Android 上觸發“嗡嗡”振動,而在 iOS 上不會。
在 iOS 上滾動選擇器專案會觸發“輕微衝擊”敲擊,而在 Android 上沒有反饋。
文字編輯
#Material 和 Cupertino 文字輸入欄位都支援拼寫檢查,並適應使用平臺的正確拼寫檢查配置以及正確的拼寫檢查選單和高亮顏色。
Flutter 還會在編輯文字欄位的內容時進行以下適配以匹配當前平臺。
鍵盤手勢導航
#在 Android 上,可以在軟鍵盤的 space 鍵上進行水平滑動以在 Material 和 Cupertino 文字欄位中移動游標。
在具有 3D Touch 功能的 iOS 裝置上,可以在軟鍵盤上進行強制按壓拖動勢來透過浮動游標以 2D 方式移動游標。這適用於 Material 和 Cupertino 文字欄位。

Android 空格鍵游標移動

iOS 3D Touch 拖動游標移動
文字選擇工具欄
#使用 Material on Android 時,在文字欄位中進行文字選擇時會顯示 Android 樣式的選擇工具欄。
使用 Material on iOS 或使用 Cupertino 時,在文字欄位中進行文字選擇時會顯示 iOS 樣式的選擇工具欄。

Android 文字選擇工具欄

iOS 文字選擇工具欄
單擊手勢
#使用 Material on Android 時,在文字欄位中單擊會將游標放置在單擊的位置。
摺疊的文字選擇還會顯示一個可拖動的控制代碼以隨後移動游標。
使用 Material on iOS 或使用 Cupertino 時,在文字欄位中單擊會將游標放置在點選單詞的最近邊緣。
iOS 上沒有可拖動的控制代碼的摺疊文字選擇。

Android 點選

iOS 點選
長按手勢
#使用 Material on Android 時,長按會選擇長按下的單詞。釋放時會顯示選擇工具欄。
使用 Material on iOS 或使用 Cupertino 時,長按會將游標放置在長按的位置。釋放時會顯示選擇工具欄。

Android 長按

iOS 長按
長按拖動手勢
#使用 Material on Android 時,在按住長按的情況下拖動會擴充套件所選單詞。
使用 Material on iOS 或使用 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 上關於文字欄位的討論。您可以在討論中留下反饋或提問。