使用 WindowInsetsCompat,應用程式可以查詢及控制螢幕小鍵盤 (也稱為 IME),類似於與系統資訊列互動的方式。應用程式也可以使用 WindowInsetsAnimationCompat,在開啟或關閉螢幕鍵盤時,建立流暢的轉場效果。
必要條件
設定螢幕鍵盤的控制項和動畫前,請先將應用程式設為無邊框顯示。這樣一來,系統就能處理系統視窗插邊,例如系統資訊列和螢幕小鍵盤。
預設同步插邊動畫
從 API 級別 37.2 以上版本開始,如果應用程式使用預設視窗版面配置調整 (例如使用 WindowInsetsController.show(WindowInsets.Type.ime()) 顯示 IME 時,使用 SOFT_INPUT_ADJUST_RESIZE 或 SOFT_INPUT_ADJUST_PAN),但未實作自訂 WindowInsetsAnimationCompat.Callback,則可受益於系統層級的同步逐格動畫。啟用後,這項功能會自動在每個影格上套用插邊,並觸發版面配置傳遞,在鍵盤移動時建立流暢的轉場效果。
如要啟用這項行為,請在 AndroidManifest.xml 檔案中,將下列 <property> 標記新增至活動或應用程式:
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
android:value="true" />
在主執行緒外執行插邊動畫
根據預設,註冊 WindowInsetsAnimationCompat.Callback 會強制視窗插邊動畫在應用程式的主要執行緒上執行。如果應用程式在動畫期間持續更新及同步處理檢視畫面版面配置 (例如在 onProgress 方法中),就必須執行這項操作。不過,如果應用程式只需要監聽生命週期轉換 (例如 IME 開始或結束動畫時),且不需要逐影格更新檢視畫面,在主執行緒上執行可能會造成不必要的負擔和動畫卡頓,尤其是在主執行緒忙碌時。
從 API 級別 37.2 開始,您可以使用 ViewTreeObserver.WindowInsetsAnimationListener 觀察這些轉換。由於這個事件監聽器不會收到逐格進度更新 (沒有 onProgress 回呼),系統可以在專屬動畫執行緒 (而非主執行緒) 上執行插邊動畫,進而提供更流暢的轉場效果。
Kotlin
val listener = object : ViewTreeObserver.WindowInsetsAnimationListener { override fun onPrepare(animation: WindowInsetsAnimation) { // Handle preparation before animation starts } override fun onEnd(animation: WindowInsetsAnimation) { // Clean up temporary changes } } // Add the listener view.viewTreeObserver.addWindowInsetsAnimationListener(listener) // Remove the listener when no longer needed view.viewTreeObserver.removeWindowInsetsAnimationListener(listener)
Java
ViewTreeObserver.WindowInsetsAnimationListener listener = new ViewTreeObserver.WindowInsetsAnimationListener() { @Override public void onPrepare(@NonNull WindowInsetsAnimation animation) { // Handle preparation before animation starts } @Override public void onEnd(@NonNull WindowInsetsAnimation animation) { // Clean up temporary changes } }; // Add the listener view.getViewTreeObserver().addWindowInsetsAnimationListener(listener); // Remove the listener when no longer needed view.getViewTreeObserver().removeWindowInsetsAnimationListener(listener);
檢查鍵盤軟體顯示設定
使用 WindowInsets 檢查螢幕鍵盤是否顯示。
Kotlin
val insets = ViewCompat.getRootWindowInsets(view) ?: return val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
Java
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view); boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
或者,您也可以使用 ViewCompat.setOnApplyWindowInsetsListener 觀察螢幕鍵盤顯示設定的變更。
Kotlin
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom insets }
Java
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> { boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; return insets; });
同步處理動畫與螢幕鍵盤
使用者輕觸文字輸入欄位時,鍵盤會從畫面底部滑入,如以下範例所示:
圖 2 中標示為「未同步」的範例顯示 Android 10 (API 級別 29) 的預設行為,其中應用程式的文字欄位和內容會貼齊到位,而不是與鍵盤的動畫同步,這種行為可能會造成視覺上的不協調。
在 Android 11 (API 級別 30) 以上版本中,您可以使用
WindowInsetsAnimationCompat,同步處理應用程式的轉換,以及鍵盤從螢幕底部向上和向下滑動。這樣看起來會更流暢,如圖 2 中標示為「Synchronized」的範例所示。
設定 WindowInsetsAnimationCompat.Callback,讓檢視畫面與鍵盤動畫同步。
Kotlin
ViewCompat.setWindowInsetsAnimationCallback( view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) { // Override methods. } )
Java
ViewCompat.setWindowInsetsAnimationCallback( view, new WindowInsetsAnimationCompat.Callback( WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP ) { // Override methods. });
WindowInsetsAnimationCompat.Callback 中有幾種方法可以覆寫,分別是 onPrepare()、onStart()、onProgress() 和 onEnd()。在變更任何版面配置前,請先呼叫 onPrepare()。
onPrepare 會在插邊動畫開始時,以及檢視畫面因動畫而重新排列前呼叫。您可以使用這個屬性儲存起始狀態,在本例中為檢視畫面的底部座標。

onPrepare() 記錄開始狀態。以下程式碼片段範例顯示對 onPrepare 的呼叫:
Kotlin
var startBottom = 0f override fun onPrepare( animation: WindowInsetsAnimationCompat ) { startBottom = view.bottom.toFloat() }
Java
float startBottom; @Override public void onPrepare( @NonNull WindowInsetsAnimationCompat animation ) { startBottom = view.getBottom(); }
系統會在插邊動畫開始時呼叫 onStart。您可以使用這個方法,將所有檢視畫面屬性設為版面配置變更的結束狀態。如果您已將 OnApplyWindowInsetsListener 回呼設為任何檢視畫面,系統此時就會呼叫該回呼。此時適合儲存檢視區塊屬性的結束狀態。
onStart() 記錄結束狀態。以下程式碼片段範例顯示對 onStart 的呼叫:
Kotlin
var endBottom = 0f override fun onStart( animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat ): WindowInsetsAnimationCompat.BoundsCompat { // Record the position of the view after the IME transition. endBottom = view.bottom.toFloat() return bounds }
Java
float endBottom; @NonNull @Override public WindowInsetsAnimationCompat.BoundsCompat onStart( @NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds ) { endBottom = view.getBottom(); return bounds; }
onProgress 會在插邊因動畫執行而變更時呼叫,因此您可以覆寫該方法,並在鍵盤動畫期間的每個影格收到通知。更新檢視區塊屬性,讓檢視區塊與鍵盤同步顯示動畫。
到這裡,版面配置變更就完成了。舉例來說,如果您使用 View.translationY 移動檢視畫面,則每次呼叫這個方法時,值都會逐漸減少,最後達到原始版面配置位置的 0。
onProgress() 同步處理動畫。以下程式碼片段範例顯示對 onProgress 的呼叫:
Kotlin
override fun onProgress( insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat> ): WindowInsetsCompat { // Find an IME animation. val imeAnimation = runningAnimations.find { it.typeMask and WindowInsetsCompat.Type.ime() != 0 } ?: return insets // Offset the view based on the interpolated fraction of the IME animation. view.translationY = (startBottom - endBottom) * (1 - imeAnimation.interpolatedFraction) return insets }
Java
@NonNull @Override public WindowInsetsCompat onProgress( @NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations ) { // Find an IME animation. WindowInsetsAnimationCompat imeAnimation = null; for (WindowInsetsAnimationCompat animation : runningAnimations) { if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { imeAnimation = animation; break; } } if (imeAnimation != null) { // Offset the view based on the interpolated fraction of the IME animation. view.setTranslationY((startBottom - endBottom) * (1 - imeAnimation.getInterpolatedFraction())); } return insets; }
您也可以覆寫 onEnd。系統會在動畫結束後呼叫這個方法。現在是清除所有暫時變更的好時機。
其他資源
- GitHub 上的 WindowInsetsAnimation。