Android 11 中的新功能之一是可讓應用在對於屏幕上的軟鍵盤打開和關閉的過程建立無縫過渡的動畫效果,這一功能源自 Android 11 中對 WindowInsets API 的大量改進。html
在 Android 11 上有兩個針對該功能的例子——這個功能已經被集成到 Google Search 應用和 Messages 應用中了:java
兩個 Android 11 中軟鍵盤動畫效果的示例: Google Search 應用 (左),Messages (右)android
讓咱們來看看如何在您的應用中添加這種用戶體驗。總共分爲三步:app
上面的每一步都環環相扣,因此咱們會在不一樣的文章中分別介紹。在這個系列的第一部中,咱們會介紹如何實現邊到邊,以及 Android 11 中相關 API 的改動。編輯器
去年咱們介紹了一個關於實現 "邊到邊" 的概念,這個方法可讓應用深度利用 Android 10 的手勢導航: 開啓全面屏體驗 | 手勢導航 (一)。ide
簡單回顧一下,實現 "邊到邊" 會讓您的應用渲染在系統狀態欄的後面,如上圖所示。函數
引用去年我本身的話:佈局
實現從邊到邊的全面屏體驗後,系統欄會覆蓋在應用內容前方。應用也得以經過更大幅面的內容爲用戶帶來更具備衝擊力的體驗。post
其實,實現邊到邊不僅僅只是在狀態欄和導航欄以後渲染。應用自己須要開始負責處理那些跟應用重疊的系統 UI 的部分。動畫
正如咱們前面提到的,兩個最直觀的例子是狀態欄和導航欄。除此以外還有軟鍵盤,有時候也叫 IME (輸入法編輯器),這是另一個咱們須要瞭解的系統 UI 。
若是咱們回想 去年的介紹,實現邊到邊能夠分爲三步:
咱們會跳過第一步,由於從去年至今這個部分沒有改動。教程中的第二步和第三步有一些針對 Android 11 的改動,讓咱們來看一下。
在以往的第二步中,應用須要使用 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 版本里也包含了這個函數。
以上就是第二步的改動。
如今讓咱們來看一下第三步: 避免與系統 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 已經被棄用了,取而代之的一些新函數來查詢不一樣類型的邊襯區:
咱們剛剛屢次提到 "類型",它們在 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()) }
這會兒那些敏銳的 👀 可能已經開始盯着這個類型列表,尤爲是其中的 軟鍵盤類型)。
在姍姍來遲了十年後,咱們終於能夠回答這個關於如何查看軟鍵盤可見性的 StackOverflow 問題。🎉
爲了獲取當前軟鍵盤的可見性,咱們能夠取得根視窗的邊襯區,而後執行 isVisible() 函數並傳入 IME) 類型。
一樣地,若是咱們想查出高度,咱們也能夠經過相同的方法實現:
val insets = ViewCompat.getRootWindowInsets(view) val imeVisible = insets.isVisible(Type.ime()) val imeHeight = insets.getInsets(Type.ime()).bottom
若是咱們須要監聽軟鍵盤的改變,咱們能夠照常使用 OnApplyWindowInsetsListener,而且使用一樣的函數:
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> val imeVisible = insets.isVisible(Type.ime()) val imeHeight = insets.getInsets(Type.ime()).bottom }
既然咱們正在回答 StackOverflow 上的問題,來看一下這個 11 年前關於如何關閉軟鍵盤的問題。
這一次咱們要介紹 Android 11 的一個新 API,它叫 WindowInsetsController。
應用能夠從任何視圖得到一個控制器,而後咱們就能夠經過傳入 IME 類型,並執行 show()) 或者 hide()) 函數來實現顯示或隱藏軟鍵盤:
val controller = view.windowInsetsController // 顯示軟鍵盤( IME ) controller.show(Type.ime()) // 隱藏軟鍵盤 controller.hide(Type.ime())
然而,這個控制器不僅僅能控制隱藏和顯示軟鍵盤...
以前咱們提到過,有一些 View.SYSTEM_UI_*
標誌已經在 Android 11 中被棄用,而且被新的 API 代替。還有一些 View.SYSTEM_UI
標誌原本是被用來改變系統 UI 的外觀和可見性的,包括:
和以前的標誌相似,這些也都在 API 30 中被棄用,並被 WindowInsetsController 中的 API 代替。
接下來咱們會經過幾個常見的應用場景來介紹如何更新這些標誌,而不是一一介紹全部這些標誌的改變:
如圖所示,這個繪圖應用隱藏了系統 UI 來讓繪圖區域最大化:
Markers 應用,展現隱藏系統 UI
爲了實現這個效果,咱們像之前同樣使用 WindowInsetsController 來執行 hide()) 和 show()) 函數,可是這一次咱們要傳入系統欄類型:
val controller = view.windowInsetsController // 當咱們想隱藏系統欄 controller.hide(Type.systemBars()) // 當咱們想顯示系統欄 controller.show(Type.systemBars())
應用使用 沉浸模式 來讓用戶在系統欄隱藏的時候能夠經過滑動來召回系統欄。爲了實現這個效果,咱們使用 WindowInsetsController 而且改變 setSystemBarsBehavior()) 爲 BEHAVIOR_SHOW_BARS_BY_SWIPE:
val controller = view.windowInsetsController // 如今開始沉浸式.. controller.setSystemBarsBehavior( WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE ) // 當咱們想要隱藏系統欄 controller.hide(Type.systemBars())
相似地,若是您以前使用吸附式的 沉浸模式,這個如今也能夠用 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 來實現:
val controller = view.windowInsetsController // 如今開始吸附式沉浸式體驗 ... controller.setSystemBarsBehavior( BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE ) // 當咱們想要隱藏系統欄 controller.hide(Type.systemBars())
接下來的這個應用場景是圍繞着狀態欄內容的顏色。您會看到以下兩個應用:
兩個應用,左邊的使用的是深色狀態欄背景,右邊的使用的是淺色背景
左邊的應用使用的是一個深色的狀態欄背景,而它的內容用的是淺色,好比時間和圖標。可若是咱們想實現一個淺色的狀態欄背景而且搭配深色的內容,像右邊顯示的同樣,咱們也可使用 WindowInsetsController。
要實現這個效果,咱們可使用 setSystemBarsAppearance()) 函數,傳入 APPEARANCE_LIGHT_STATUS_BARS 值:
val controller = view.windowInsetsController // 啓用淺色狀態欄內容 controller.setSystemBarsAppearance( APPEARANCE_LIGHT_STATUS_BARS, // value APPEARANCE_LIGHT_STATUS_BARS // mask )
但若是您想設置一個深色的狀態欄,能夠傳入 0,而不是清除那個值。
注意: 您也能夠在主題中經過設置
android:windowLightStatusBar
實現上述效果。在您知道這個值不會變更的狀況下,這個方式可能更好。
APPEARANCE_LIGHT_NAVIGATION_BARS 標誌能夠給導航欄提供相似的功能。
惋惜的是這個 API 的 Jetpack 版本尚未上線,而咱們正在加緊準備,敬請關注。
咱們的第一步完成了。在本系列下一篇文章中,咱們會研究第二步: 應用對於邊襯區的響應式動畫。敬請關注。