Android App 的 Edge-to-Edge UI 指南 Part 2:軟體鍵盤 IME 動畫處理
在上一篇 Part 1 的介紹裡,我們看見了軟體鍵盤,或是包含手寫或語音輸入等輸入方式,以下統稱 IME(輸入法,Input Method Editor),其所佔的空間也可以從 Window Insets 裡取得,因此當 IME 彈出時,app layout 可以因應調整。
下面動畫的左邊部分,是我們在 Part 1 時的進度,可以看見 IME 出現時,輸入框往上位移以避開被鍵盤蓋住,但是輸入框的位移並沒有跟 IME 動畫同步;而本文希望做到的是右邊的樣子,讓 app layout 可以配合 IME 動畫做調整。
先觀察一下 API 的設計
這次除了 OnApplyWindowInsetsListener
以外,我們額外再註冊一個 WindowInsetsAnimationCompat.Callback
,印些 log 來觀察一下:
當點擊輸入框彈出 IME 時,得到的結果是:
onApplyWindowInsets, bottom = 0onPrepareonApplyWindowInsets, bottom = 817onStart, bounds = Bounds{lower=Insets{left=0, top=0, right=0, bottom=0} upper=Insets{left=0, top=0, right=0, bottom=817}}onProgress, bottom = 0(多個 onProgress,0~817)onProgress, bottom = 817onEnd
之前的做法我們只聽得到 onApplyWindowInsets
,所以 IME 彈出瞬間直接設了 817px 的 padding 上去,輸入框瞬間位移。
但是如果我們在 onApplyWindowInsets
和 onProgress
時都調整 layout,聽到的數值會是 0、817、0、1、2……816、817,輸入框還是會先閃現在終點,才又回到起點開始動畫。
當試著切換 IME,例如從英文鍵盤換到注音鍵盤時,由於注音鍵盤較高,高度改變時會發生:
onApplyWindowInsets, bottom = 930
IME 切換時是沒有動畫的,所以只有 onApplyWindowInsets
會被呼叫,因此我們也不能忽略 onApplyWindowInsets
只管 onProgress
。
實作
透過上面的觀察,我們可以在 onPrepare
的時候視為動畫開始進行,而動畫進行中時忽略 onApplyWindowInsets
,只處理 onProgress。
有了上面的實作,只要將 Part 1 實作裡的
ViewCompat.setOnApplyWindowInsetsListener(view)
換成
view.doOnWindowInsetsChanged(listenToAnimation = true)
就可以讓 UI 與 IME 動畫同步了。
包裝成 extension function 的做法整合了OnApplyWindowInsetsListener
跟 WindowInsetsAnimationCompat.Callback
,讓使用端寫起來更簡潔,也可以透過 listenToAnimation
控制要不要聽取動畫進度。
相容性
Window Insets Animation 從 Android 11(API level 30)開始支援,但這裡的實作使用的都是 AndroidX 的 ViewCompat
及 WindowInsetsCompat
的相容實作,因此雖然舊版系統不會因此實現 IME 動畫同步,但你無需為了相容舊裝置實作額外的邏輯。
未來展望
你可能已經注意到,雖然上述的 API 就目前來說,使用情境就只有處理 IME 的動畫,但 API 的設計本身並不是針對 IME,而是各種 insets 都有實作動畫的空間。
撰稿期間正好碰上 Android 12.1(12L)剛發布不久,其中底部的工作列(taskbar)對 app 來說也是一種 system bar inset。將來若有 IME 以外的 insets 在出現、隱藏或改變大小時回報動畫給 app,也是相當合理;而本文介紹的實作有鑑於此,也並非只有針對 IME 或 bottom inset 做處理。
Dcard Android 職缺看這裡
Senior Android Developer 👉 https://dcard.link/PnSmCJ
Senior Android Developer, Advertising 👉 https://dcard.link/d6ZH2s
Junior Android Developer 👉 https://dcard.link/hU0Cd9