WebView 會使用兩個可視區域管理內容對齊方式:版面配置可視區域 (網頁大小) 和視覺可視區域 (使用者實際看到的網頁部分)。版面配置檢視區塊通常是靜態的,但當使用者縮放、捲動或系統 UI 元素 (例如螢幕鍵盤) 出現時,視覺檢視區塊會動態變更。
功能相容性
隨著時間演進,WebView 對視窗插邊的支援也隨之發展,以符合原生 Android 應用程式的預期,並與網頁內容行為保持一致:
| Milestone | 已新增功能 | 範圍 |
|---|---|---|
| M136 | displayCutout() 和 systemBars() 支援,方法是透過 CSS safe-area-insets。 |
僅限全螢幕 WebView。 |
| M139 | ime() (輸入法編輯器,即鍵盤) 支援,方法是調整可視區域大小。 |
所有 WebView。 |
| M144 | displayCutout() 和 systemBars() 支援。 |
所有 WebView (不論全螢幕狀態為何)。 |
詳情請參閱「WindowInsetsCompat」。
核心機制
WebView 會透過兩種主要機制處理插邊:
安全區域 (
displayCutout、systemBars):WebView 會透過 CSS safe-area-inset-* 變數,將這些維度轉送至網頁內容。開發人員可藉此避免自己的互動式元素 (例如導覽列) 遭到凹口或狀態列遮蔽。使用輸入法編輯器 (IME) 調整視覺可視區域大小:從 M139 開始,輸入法編輯器 (IME) 會直接調整視覺可視區域大小。這項大小調整機制也是以 WebView-Window 交集為依據。舉例來說,在 Android 多工模式中,如果 WebView 的底部延伸至視窗底部下方 200 dp,則視覺檢視區塊會比 WebView 的大小小 200 dp。這種視覺檢視區塊大小調整 (適用於 IME 和 WebView 視窗交集) 只會套用至 WebView 底部。這項機制不支援調整左側、右側或頂端重疊的大小。也就是說,出現在這些邊緣的固定鍵盤不會觸發可視區域大小調整。
先前,視覺可視區域會保持固定,導致輸入欄位經常隱藏在鍵盤後方。調整可視區域大小後,網頁可見部分預設會變成可捲動,確保使用者能看到遭遮蔽的內容。
界線和重疊邏輯
只有在系統 UI 元素 (資訊列、螢幕凹口或鍵盤) 直接與 WebView 的螢幕界限重疊時,WebView 才應收到非零的插邊值。如果 WebView 與這些 UI 元素沒有重疊 (例如 WebView 位於畫面中央,且未觸及系統資訊列),則應會收到零值插邊。
如要覆寫這項預設邏輯,並提供網頁內容完整的系統尺寸 (無論是否重疊),請使用 setOnApplyWindowInsetsListener 方法,並從監聽器傳回原始的未修改 windowInsets 物件。提供完整的系統尺寸,可讓網頁內容與裝置硬體對齊,確保設計一致性,無論 WebView 目前的位置為何皆是如此。這樣可確保 WebView 在移動或擴展至觸碰螢幕邊緣時,能順暢轉換。
Kotlin
ViewCompat.setOnApplyWindowInsetsListener(myWebView) { _, windowInsets -> // By returning the original windowInsets object, we override the default // behavior that zeroes out system insets (like system bars or display // cutouts) when they don't directly overlap the WebView's screen bounds. windowInsets }
Java
ViewCompat.setOnApplyWindowInsetsListener(myWebView, (v, windowInsets) -> { // By returning the original windowInsets object, we override the default // behavior that zeroes out system insets (like system bars or display // cutouts) when they don't directly overlap the WebView's screen bounds. return windowInsets; });
管理調整大小事件
由於鍵盤顯示時會觸發可視區域大小調整,網頁程式碼可能會看到更頻繁的調整大小事件。開發人員必須確保程式碼不會因清除元素焦點而對這些大小調整事件做出反應。這麼做會造成焦點遺失和鍵盤關閉的迴圈,導致使用者無法輸入內容:
- 使用者著重在輸入元素。
- 鍵盤隨即顯示,並觸發大小調整事件。
- 網站的程式碼會清除焦點,以回應大小調整。
- 鍵盤會因失去焦點而隱藏。
如要減輕這種行為,請檢查網頁端監聽器,確保視埠變更不會意外觸發 blur() JavaScript 函式或焦點清除行為。
實作插邊處理
WebView 的預設設定會自動適用於大多數應用程式。不過,如果應用程式使用自訂版面配置 (例如,您新增自己的邊框間距來考量狀態列或鍵盤),可以採用下列方法,改善網頁內容和原生 UI 的協同運作方式。如果原生 UI 會根據 WindowInsets 對容器套用邊框間距,您必須在這些插邊到達 WebView 前正確管理,以免出現雙重邊框間距。
如果原生版面配置和網頁內容套用相同的插邊尺寸,就會出現雙倍邊框間距,導致間隔多餘。舉例來說,假設手機的狀態列為 40 像素,原生檢視區塊和 WebView 都會看到 40 像素的插邊。兩者都會新增 40 像素的邊框間距,因此使用者會在頂端看到 80 像素的間隙。
「歸零」做法
為避免雙重邊框間距,請務必在原生檢視區塊使用插邊維度做為邊框間距後,使用新 WindowInsets 物件上的 Insets.NONE 將該維度重設為零,再將修改後的物件傳遞至 WebView 的檢視區塊階層。
將邊框間距套用至父項檢視區塊時,一般應使用歸零方法,也就是設定 Insets.NONE,而非 WindowInsetsCompat.CONSUMED。在某些情況下,傳回 WindowInsetsCompat.CONSUMED 可能會有效。
不過,如果應用程式的處理常式變更插邊或新增自己的邊框間距,可能會發生問題。歸零法沒有這些限制。
將插邊歸零,避免出現空白間距
如果應用程式先前已傳遞未使用的插邊,或插邊發生變化 (例如鍵盤隱藏),您在這種情況下使用插邊,會導致 WebView 無法收到必要的更新通知。這可能會導致 WebView 保留先前狀態的虛影邊框間距 (例如在鍵盤隱藏後,仍保留鍵盤邊框間距)。
以下範例顯示應用程式與 WebView 之間的互動中斷:
- 初始狀態:應用程式一開始會將未使用的插邊 (例如
displayCutout()或systemBars()) 傳遞至 WebView,後者會在內部將邊框間距套用至網頁內容。 - 狀態變更和錯誤:如果應用程式變更狀態 (例如鍵盤隱藏),且應用程式選擇傳回
WindowInsetsCompat.CONSUMED來處理產生的插邊。 - 通知遭到封鎖:如果取用插邊,Android 系統就無法將必要的更新通知傳送至 WebView 的檢視區塊階層。
- 幽靈邊框間距:由於 WebView 未收到更新,因此會保留先前狀態的邊框間距,導致出現幽靈邊框間距 (例如,在鍵盤隱藏後仍保留鍵盤邊框間距)。
請改用 WindowInsetsCompat.Builder 將處理的型別設為零,再將物件傳遞至子項檢視區塊。這會通知 WebView,啟用通知繼續向下傳送至檢視區塊階層時,已將這些特定插邊納入考量。
Kotlin
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets -> // 1. Identify the inset types you want to handle natively val types = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() // 2. Extract the dimensions and apply them as padding to the native container val insets = windowInsets.getInsets(types) view.setPadding(insets.left, insets.top, insets.right, insets.bottom) // 3. Return a new WindowInsets object with the handled types set to NONE (zeroed). // This informs the WebView that these areas are already padded, preventing // double-padding while still allowing the WebView to update its internal state. WindowInsetsCompat.Builder(windowInsets) .setInsets(types, Insets.NONE) .build() }
Java
ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> { // 1. Identify the inset types you want to handle natively int types = WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(); // 2. Extract the dimensions and apply them as padding to the native container Insets insets = windowInsets.getInsets(types); rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom); // 3. Return a new Insets object with the handled types set to NONE (zeroed). // This informs the WebView that these areas are already padded, preventing // double-padding while still allowing the WebView to update its internal // state. return new WindowInsetsCompat.Builder(windowInsets) .setInsets(types, Insets.NONE) .build(); });
如何選擇拒絕
如要停用這些新式行為並返回舊版檢視區塊處理方式,請執行下列操作:
截斷插邊:使用
setOnApplyWindowInsetsListener或在WebView子類別中覆寫onApplyWindowInsets。清除插邊:從頭開始傳回已耗用的插邊集 (例如
WindowInsetsCompat.CONSUMED)。這項動作可防止插邊通知完全傳播至 WebView,有效停用新式可視區域大小調整功能,並強制 WebView 保留初始可視區域大小。