自動平臺適配
瞭解更多關於 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 關於文字欄位的討論。您可以在討論中留下反饋或提出問題。