WebView 使用兩個可視區域管理內容對齊方式:版面配置可視區域 (頁面大小) 和視覺可視區域 (使用者實際看到的頁面部分)。版面配置可視區域通常是靜態的,但視覺可視區域會隨著使用者縮放、捲動或系統 UI 元素 (例如螢幕鍵盤) 出現而動態變更。
功能相容性
WebView 對視窗插邊的支援會隨著時間演進,讓網路內容行為符合原生 Android 應用程式的預期:
| Milestone | 已新增功能 | 範圍 |
|---|---|---|
| M136 | 透過 CSS safe-area-insets 支援 displayCutout() 和 systemBars()。 |
僅限全螢幕 WebView。 |
| M139 | 透過調整視覺可視區域大小,支援 ime() (輸入法編輯器,即鍵盤)。 |
所有 WebView。 |
| M144 | displayCutout()和systemBars()支援。 |
所有 WebView (無論是否為全螢幕狀態)。 |
詳情請參閱「WindowInsetsCompat」。
核心機制
WebView 透過兩種主要機制處理插邊:
安全區域 (
displayCutout、systemBars):WebView 會透過 CSS safe-area-inset-* 變數,將這些尺寸轉送至網路內容。這樣一來,開發人員就能避免自己的互動元素 (例如導覽列) 遭到凹口或狀態列遮蔽。使用輸入法編輯器 (IME) 調整視覺可視區域大小:從 M139 開始,輸入法編輯器 (IME) 會直接調整視覺可視區域大小。這項大小調整機制也以 WebView 與視窗的交集為依據。舉例來說,在 Android 多工模式下,如果 WebView 的底部延伸至視窗底部下方 200dp,則視覺可視區域會比 WebView 的大小小 200dp。這種視覺可視區域大小調整 (適用於 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,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 保留初始視覺可視區域大小。