Android 11 中的新功能之一是可以讓應用在對于螢幕上的軟鍵盤打開和關閉的過程建立無縫過渡的動畫效果,這一功能源自 Android 11 中對 WindowInsets API 的大量改進。
在 Android 11 上有兩個針對該功能的例子——這個功能已經被內建到 Google Search 應用和 Messages 應用中了:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICMyYTMvw1dvwlMvwlM3VWaWV2Zh1Wa-YWan5Se2ZTZ5QHZ4hjdvwFNzIzN2IjMtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.gif)
兩個 Android 11 中軟鍵盤動畫效果的示例: Google Search 應用 (左),Messages (右)
讓我們來看看如何在您的應用中添加這種使用者體驗。總共分為三步:
- 首先,我們需要做到 "邊到邊" (edge-to-edge);
- 第二步,應用需要針對邊襯區動畫做出反應;
- 最後第三步就是應用在恰當的場景中控制并使用邊襯區動畫。
上面的每一步都環環相扣,是以我們會在不同的文章中分别介紹。在這個系列的第一部中,我們會介紹如何實作邊到邊,以及 Android 11 中相關 API 的改動。
實作邊到邊 (edge-to-edge)
去年我們介紹了一個關于實作 "邊到邊" 的概念,這個方法可以讓應用深度利用 Android 10 的手勢導航: 開啟全面屏體驗 | 手勢導航 (一)。
簡單回顧一下,實作 "邊到邊" 會讓您的應用渲染在系統狀态欄的後面,如上圖所示。
引用去年我自己的話:
實作從邊到邊的全面屏體驗後,系統欄會覆寫在應用内容前方。應用也得以通過更大幅面的内容為使用者帶來更具有沖擊力的體驗。
實作邊到邊跟軟鍵盤有什麼關系?
其實,實作邊到邊不單單隻是在狀态欄和導航欄之後渲染。應用本身需要開始負責處理那些跟應用重疊的系統 UI 的部分。
正如我們前面提到的,兩個最直覺的例子是狀态欄和導航欄。除此之外還有軟鍵盤,有時候也叫 IME (輸入法編輯器),這是另外一個我們需要了解的系統 UI 。
應用如何實作邊到邊?
如果我們回想 去年的介紹,實作邊到邊可以分為三步:
- 改變系統欄的顔色
- 設定全屏布局
- 處理視覺沖突
我們會跳過第一步,因為從去年至今這個部分沒有改動。教程中的第二步和第三步有一些針對 Android 11 的改動,讓我們來看一下。
#2: 設定全屏布局
在以往的第二步中,應用需要使用 systemUiVisibility API 以及一些參數來設定全屏布局:
view.systemUiVisibility =
// 通知系統,視窗希望在極端的情況下該如何布局内容。檢視文檔來擷取更具體的資訊。
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
// 通知系統,視窗希望在導航欄被隐藏的情況下如何布局内容。
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
複制
如果您的項目設定編譯的目标 SDK 版本已經更新為 30 并且使用這個 API ,您會發現這些 API 都已經被标示為棄用了。
它們已經被 Window 的一個叫作
setDecorFitsSystemWindows()
的函數替代了:
// 通知視窗,我們(應用)會處理任何系統視窗(而不是 decor)
window.setDecorFitsSystemWindows(false)
// 或者您可以使用 AndroidX v1.5.0-alpha02 中的 WindowCompat
WindowCompat.setDecorFitsSystemWindows(window, false)
複制
取代那些參數的是一個布爾值 false,它的意思是應用會處理任何系統視窗的适配 (換句話說就是全屏)。
在 WindowCompat 中,我們還有一個 Jetpack 版本的該函數,androidx.core 庫的 v1.5.0-alpha02 版本裡也包含了這個函數。
以上就是第二步的改動。
#3: 處理視覺沖突
現在讓我們來看一下第三步: 避免與系統 UI 産生重疊,也可以說是使用視窗邊襯區來決定如何移動應用的内容來避免與系統 UI 的沖突。在 Android 系統中,邊襯區可以通過 WindowInsets 類和 AndroidX 中的 WindowInsetsCompat 來通路。
如果我們檢視 API 30 以前版本的 WindowInsets,最常用的邊襯區類型是系統視窗邊襯區。這些邊襯區包括了狀态欄、導航欄以及打開時的軟鍵盤。
為了使用 WindowInsets,您通常需要在一個視圖上添加 OnApplyWindowInsetsListener,并且在這個函數中處理傳進來的邊襯區:
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
v.updatePadding(bottom = insets.systemWindowInsets.bottom)
// 傳回邊襯區,這樣它們才能夠繼續在視圖樹中繼續傳遞下去
insets
}
複制
在這個例子中,我們擷取到 系統視窗邊襯區,然後更新視圖的内邊距,這是一個常見的應用場景。
還有一些其他類型的邊襯區,比如 Android 10 最近新增的手勢邊襯區:
ViewCompat.setOnApplyWindowInsetsListener(v) { view, windowInsets ->
val sysWindow = windowInsets.systemWindowInsets
val stable = windowInsets.stableInsets
val systemGestures = windowInsets.systemGestureInsets
val tappableElement = windowInsets.tappableElementInsets
}
複制
和 systemUiVisibility API 類似,許多 WindowInsets API 已經被棄用了,取而代之的一些新函數來查詢不同類型的邊襯區:
- getInsets(type: Int) 會傳回指定類型的可見邊襯區。
- getInsetsIgnoringVisibility(type: Int) 會傳回所有邊襯區,無論它們是否可見。
- isVisible(type: Int) 會傳回 true 如果指定的類型是可見的。
我們剛剛多次提到 "類型",它們在 WindowInsets.Type 類中被定義為函數,每個函數都會傳回一個整數标示。我們稍後還會展示如何使用 OR 位運算來查詢結合到一起的類型。
所有這些 API 都已經被添加到 AndroidX Core 中的 WindowInsetsCompat,并且向前相容到 API 14 (請檢視 發行注記 來擷取更多資訊)。
再來看如果我們用新的 API 來更新之前的示例,它們就變成:
ViewCompat.setOnApplyWindowInsetsListener(...) { view, insets ->
- val sysWindow = insets.systemWindowInsets
+ val sysWindow = insets.getInsets(Type.systemBars() or Type.ime())
- val stable = insets.stableInsets
+ val stable = insets.getInsetsIgnoringVisibility(Type.systemBars())
- val systemGestures = insets.systemGestureInsets
+ val systemGestures = insets.getInsets(Type.systemGestures())
- val tappableElement = insets.tappableElementInsets
+ val tappableElement = insets.getInsets(Type.tappableElement())
}
複制
軟鍵盤類型 ⌨️
這會兒那些敏銳的