Android App 的 Edge-to-Edge UI 指南 Part 1:基礎實作與相容性

工程師 Hiking
Dcard Tech Blog
Published in
7 min readMar 23, 2022

什麼是 Edge-to-Edge UI?

在傳統的實作上,system bars(包含 status bar 和 navigation bar)通常不包含在 Activity 的 layout 範圍裡,而是 app 跟系統宣告 system bars 的顏色,系統則透過 DecorView 去繪製這些區塊。因此,這些區塊往往是由色塊組成,可以看出它們跟 app 內容之間的邊界。

而 edge-to-edge 的精神在於把 app 本身(Activity、Fragment 等)的 layout 範圍延伸到 system bars 後面,將 system bars 融入 app 的 layout 設計裡(例如 hero image 可以延伸到 status bar 後面),navigation bar 也不再是黑色的「海苔條」。

System bars 在畫面上的範圍

自從 Android 10 官方推出手勢導覽後,官方即建議 app 開發者實作 edge-to-edge UI。概念上類似當初 iOS 推出手勢導覽時提出的 Safe Area,但 API 的設計上有所不同。

實作 edge-to-edge UI 的差異

實作步驟

1. 調整 Activity 的 Theme

首先,為了讓 app 內容可以繪製到 system bars 的區域,我們需要將 status bar 和 navigation bar 的背景都設為透明。

除此之外,為了讓 system bars 融入 app,會需要讓 status bar 和 navigation bar 的主題和 app 同步;假如你的 app 支援深淺色主題,你可能會想要分開定義不同的主題並設定不同的值,或是用 DayNight 主題並搭配 attribute 也可以。

假如你的 app 支援的 API level 較低,這裡用到的 attributes 有些會需要處理相容性問題,這部分稍後會提到。

2. 將 Activity 的 layout(Decor)延伸到 system window 範圍

我們需要在 Activity 裡呼叫 WindowCompat.setDecorFitsSystemWindows ,讓系統知道這個 Activity 的 layout 要延伸到整個 system window 範圍。

3. 調整 views 以適應 window insets

當你將 layout 延伸到整個 system window 以後,代表 system bar 現在會直接蓋在 Activity 的 layout 上,你需要適當地設定 view paddings 以避免重要的 UI 跟 system bar 重疊。

首先,我們註冊一個 OnApplyWindowInsetsListener,以取得系統提供的 window insets。

Listener 裡可以聽到的 insets 包含了相當多元的資訊,包含 system bars、IME(輸入法、軟體鍵盤)、display cutouts(螢幕缺口)等等。

我們可以取得 system bars 和 IME 整合的結果 :

接著我們會幫 views 加上 paddings 以適應 window insets:

Window insets 有可能出現在左右兩邊,例如傳統的 3 按鈕 navigation bar。一般我們會在 Activity 的 root 直接加 paddings 處理完左右的 insets,因為可捲動的內容通常是垂直的,左右的 insets 不需要覆蓋在可捲動的內容上;但是 bottom inset 我們會把 padding 加在可捲動的 view(例如 ScrollViewRecyclerView)上,讓內容可以延伸到畫面底部。

傳統 3 按鈕導覽(導覽列在右方)
手勢導覽(導覽列在下方)

4. 決定要吸收哪些 insets

OnApplyWindowInsetsListener.onApplyWindowInsets() 的回傳值會決定要吸收(consume)哪些 insets。

當在 Activity root layout 處理完 insets 以後,若你直接回傳 WindowInsetsCompat.CONSUMED 吸收所有的 insets,所有的 children,例如底下可能有 Fragment,它就不能處理這些 insets 了;除此之外,顯示 Snackbars 時也可能會被 system bars 蓋住。

因此你可以根據情況決定要吸收哪些 insets,例如我們只想要保留 bottom inset 給 Fragment 裡的 RecyclerView 以及 Snackbars:

相容性

實作 edge-to-edge UI 因為牽涉到系統 UI 的自由度,當你的 app 支援的 API level 愈低,就愈可能會遇到一些限制,這時候你會需要配合系統去做一些處理。

2023 Update: 現在 ComponentActivity 提供了 enableEdgeToEdge(),可以直接使用,就不需要自己處理下列的版本相容問題。

Navigation Bar 的背景色

這裡先簡單整理關於 navigation bar 的 API level:

  • 設定背景色 —— API 21
  • 設定為淺色主題(按鈕圖示為深色) —— API 26 / API 27 (XML)
  • 手勢導覽 —— API 30
API ≤ 22/API ≥ 26/API ≥ 29

前面我們直接將 navigation bar 的背景設為透明,並且壓在可捲動的內容上。以 API ≥ 29 的手勢導覽來說,因為沒有按鈕且 UI 只有繪製一條橫槓,直接疊在內容沒有太大問題;而 API ≥ 29如果使用者選擇 3 按鈕的 navigation bar,即使我們設定背景為透明,系統依然會自動加上半透明背景,以確保按鈕圖示不會跟後面的內容互相干擾(也可以關掉)。

但是面對 API ≤ 29 時,系統不會自動幫你加背景,所以你需要自己加上適合當前主題的深色或淺色背景。而面對 API ≤ 22 時,由於 navigation bar 不支援淺色模式,在淺色主題下你也必須設定深色背景。

系統 UI 相關的 API level 需求總整理

參考資料

延伸閱讀

--

--