14.3 C
New York
Tuesday, March 25, 2025

Make WebViews edge-to-edge. Make your WebViews appropriate with… | by Ash Nohe | Android Builders | Mar, 2025


Guarantee your WebViews are appropriate with Android 16, as Android 16 removes the power to opt-out of drawing your app edge-to-edge. The best way you deal with insets for WebViews relies on whether or not or not your app owns the net content material.

This publish assumes fundamental data of dealing with insets and is relevant in case your WebViews are drawing beneath system bars or show cutouts. See the Compose and Views documentation, and the Inset dealing with suggestions weblog for steerage on dealing with insets.

In case you’re searching for steerage on the right way to make your webpage edge-to-edge on Chrome, see the Chrome on Android edge-to-edge migration information as a substitute.

This publish covers the next subjects:

  • Tips on how to deal with WebViews in case your app doesn’t personal the net content material
  • Tips on how to deal with WebViews in case your app owns the net content material
  • Dealing with IME insets in WebViews

WebViews displaying exterior content material can not simply draw edge-to-edge and will as a substitute be inset to keep away from the system bars and show cutout. The implementation is completely different relying on when you’re utilizing Compose or Views, however usually follows these steps:

  1. Wrap the WebView in a container and apply insets as padding on that container.
  2. Set the app’s background coloration in a best-effort try and match the webpage.
Three app screenshots showing WebView layout issues and fixes.
Determine 1. Left to proper. (a) The left picture reveals a full display screen WebView in an app focusing on SDK 35, at which level edge-to-edge is enforced. The highest of the WebView collides with standing bar icons. (b) The center picture reveals the results of padding the WebView’s mum or dad. The app’s background is about to pink to make it obvious that the WebView is inset. (c) The suitable picture is the specified consequence. The app’s background is about to white in a best-effort try and match the webpage.

See the next code samples.

Apply insets to a WebView in a Compose app

To make sure a WebView in Compose avoids overlapping with system bars, show cutout and keyboard, apply Modifier.windowInsetsPadding(WindowInsets.safeDrawing) to its wrapper (e.g. AndroidView). This offers the mandatory padding to maintain the WebView content material throughout the secure space of the display screen.

@Composable
enjoyable WebViewInCompose() {
AndroidView(
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing),
manufacturing unit = { context ->
WebView(context).apply {
// Configure WebView choices right here
}
}
)
}

Then, replace your app’s background so it matches the WebView’s background as a lot as potential.

Apply insets to a WebView in a Views app

To make sure a WebView in Views avoids overlapping the system bars, show cutout and keyboard, wrap the WebView in a container like a FrameLayout and do one of many following:

  • Set android:fitsSystemWindows="true"
  • Use ViewCompat.setOnApplyWindowInsetsListener() and apply systemBars, displayCutout, and ime insets on the container. See the Views documentation for a code pattern utilizing the listener.

Lastly, replace android:windowBackground in your themes.xml so it matches the WebView’s background if potential.

In case your app owns the net content material, both pad the WebView’s mum or dad as described above; or, use JavaScript to inject padding into the net web page to attract the web page edge-to-edge as described under.

To inject padding into the net web page, first add to your HTML.

Second, outline CSS variables for prime, proper, backside and left secure space insets, because the env(safe-area-inset-*) variables return 0px on Android on the time of scripting this weblog.

/* CSS */
physique {
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
}

As a substitute of making use of insets on the WebView’s container, use JavaScript to move the insets to your CSS variables to pad the webpage. The implementation is completely different relying on when you’re utilizing Compose or Views, however it usually follows these steps:

  1. Retrieve the highest, proper, backside and left system bar, show cutout and IME insets as uncooked pixel values.
  2. Convert the uncooked pixel values to density impartial pixels.
  3. Inject the density impartial pixels into the CSS variables as CSS pixels. When the web site renders on the Android system, the WebView converts the CSS pixels again to density impartial pixels. See Help completely different screens in internet apps for extra info.
  4. In Compose, recompose when the software program keyboard expands or collapses if relevant.
  5. In Views, dispatch insets when the webpage first masses.
Two app screenshots. Top: WebView overlapping system bars and cutout. Bottom: WebView correctly padded using JavaScript.
Determine 2. Prime to backside. (a) The highest picture reveals a full display screen WebView in an app focusing on SDK 35, at which level edge-to-edge is enforced. The WebView collides with system bars and show cutout. (b) The underside picture reveals the results of utilizing JavaScript to inject padding into the webpage, which is the edge-to-edge consequence we wish.

See the next code samples.

Utilizing Compose and JavaScript to inject insets into internet pages

A code pattern displaying the right way to use JavaScript to inject insets into webpages in a Compose app.

import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.basis.format.WindowInsets
import androidx.compose.basis.format.ime
import androidx.compose.basis.format.safeDrawing
import androidx.compose.ui.unit.Dp
...

@Composable
enjoyable MainScreen() {
Field(modifier = Modifier.fillMaxSize()) {

// Retrieve insets as uncooked pixels
val insets = WindowInsets.safeDrawing
WebViewInCompose(
initialUrl = "file:///android_asset/instance.html",
insets = insets
)
}
}

@SuppressLint("SetJavaScriptEnabled")
@Composable
enjoyable WebViewInCompose(
initialUrl: String,
insets: WindowInsets,
density: Density = LocalDensity.present,
layoutDirection: LayoutDirection = LocalLayoutDirection.present,
myWebViewClient: CustomWebViewClient = bear in mind { CustomWebViewClient(
insets, density, layoutDirection
) }
) {

// Do not apply insets to the container
AndroidView(
manufacturing unit = { context ->
WebView(context).apply {
webViewClient = myWebViewClient
settings.javaScriptEnabled = true
loadUrl(initialUrl)
}
}, replace = { view ->

// Updates webpage when software program keyboard expands or collapses.
// In case your webpage would not have an enter discipline that opens the
// software program keyboard, take away this line.
applySafeAreaInsetsToWebView(insets, density, layoutDirection, view)
}
)
}

class CustomWebViewClient(
non-public val insets: WindowInsets,
non-public val density: Density,
non-public val layoutDirection: LayoutDirection
) : WebViewClient(){

override enjoyable onPageFinished(view: WebView?, url: String?) {
tremendous.onPageFinished(view, url)

// Inject insets into the webpage as soon as the web page has absolutely loaded
applySafeAreaInsetsToWebView(insets, density, layoutDirection, view)
}
}

non-public enjoyable applySafeAreaInsetsToWebView(
insets: WindowInsets,
density: Density,
layoutDirection: LayoutDirection,
webView: WebView?){

// Convert uncooked pixels to density impartial pixels
val prime = insets.getTop(density).toDp(density).worth
val proper = insets.getRight(density, layoutDirection).toDp(density).worth
val backside = insets.getBottom(density).toDp(density).worth
val left = insets.getLeft(density, layoutDirection).toDp(density).worth

val safeAreaJs = """
doc.documentElement.fashion.setProperty('--safe-area-inset-top', '${prime}px');
doc.documentElement.fashion.setProperty('--safe-area-inset-right', '${proper}px');
doc.documentElement.fashion.setProperty('--safe-area-inset-bottom', '${backside}px');
doc.documentElement.fashion.setProperty('--safe-area-inset-left', '${left}px');
"""

// Inject the density impartial pixels into the CSS variables as CSS pixels
webView?.evaluateJavascript(safeAreaJs, null)
}

non-public enjoyable Int.toDp(density: Density): Dp = with(density) { this@toDp.toDp() }

Utilizing Views and JavaScript to inject insets into internet pages

A code pattern displaying the right way to use JavaScript to inject insets into webpages in a Views app.

import androidx.core.util.TypedValueCompat.pxToDp
import
androidx.core.view.WindowInsetsCompat.Sort.displayCutout
import androidx.core.view.WindowInsetsCompat.Sort.ime
import androidx.core.view.WindowInsetsCompat.Sort.systemBars
...

override enjoyable onCreate(savedInstanceState: Bundle?) {
...

val myWebView: WebView = findViewById(R.id.webView)
myWebView.settings.javaScriptEnabled = true
myWebView.loadUrl("file:///android_asset/instance.html")

ViewCompat.setOnApplyWindowInsetsListener(myWebView) { _, windowInsets ->

// Retrieve insets as uncooked pixels
val safeDrawingInsets = windowInsets.getInsets(
systemBars() or displayCutout() or ime()
)
val displayMetrics = myWebView.context.assets.displayMetrics

// Convert uncooked pixels to density impartial pixels
val prime = pxToDp(safeDrawingInsets.prime.toFloat(), displayMetrics)
val proper = pxToDp(safeDrawingInsets.proper.toFloat(), displayMetrics)
val backside = pxToDp(safeDrawingInsets.backside.toFloat(), displayMetrics)
val left = pxToDp(safeDrawingInsets.left.toFloat(), displayMetrics)

val safeAreaJs = """
doc.documentElement.fashion.setProperty('--safe-area-inset-top', '${prime}px');
doc.documentElement.fashion.setProperty('--safe-area-inset-right', '${proper}px');
doc.documentElement.fashion.setProperty('--safe-area-inset-bottom', '${backside}px');
doc.documentElement.fashion.setProperty('--safe-area-inset-left', '${left}px');
"""

// Inject the density impartial pixels into the CSS variables as CSS pixels
myWebView.evaluateJavascript(safeAreaJs, null)

windowInsets
}

myWebView.webViewClient = object : WebViewClient() {
override enjoyable onPageFinished(view: WebView, url: String) {
tremendous.onPageFinished(myWebView, url)
// dispatch insets as a result of insets aren't utilized when the webpage first masses.
view.requestApplyInsets()
}
}
}

Be aware: If the injected insets look too giant, it may be as a result of your web site’s Viewport initial-scale is greater than 1. On this case, divide the highest, proper, backside, and left variables by the size.

To make sure your internet web page appropriately adjusts when the on-screen keyboard (IME) seems, you should additionally account for IME insets. The offered Compose and Views code instance already contains this by retrieving WindowInsets.safeDrawing() in Compose or WindowInsetsCompat.Sort.ime() in Views:

// Getting IME insets from the above Compose code pattern
// safeDrawing contains ime, systemBars, and displayCutout
WindowInsets.safeDrawing

// Getting IME insets from the above Views code pattern
val safeDrawingInsets = windowInsets.getInsets(
systemBars() or displayCutout() or ime()
)

Omitting IME insets will result in points the place the webpage content material is obscured by the keyboard. For instance, when the person faucets an HTML textual content enter and the IME expands, the webpage gained’t resize, stopping the person from seeing content material hidden behind the keyboard. By together with IME insets, the webpage dynamically adjusts its format when the IME is displayed, making certain all content material stays accessible to the person.

Determine 3. Left (incorrect implementation) to proper (right implementation). (a) The left GIF reveals a full-screen WebView the place IME insets aren’t dealt with. When the IME seems, the person can not scroll to view content material behind it. (b) The suitable picture reveals the identical WebView with right IME inset dealing with. After the IME seems, the person can scroll to entry the total webpage content material.

In abstract, making certain WebViews are appropriate with an edge-to-edge show requires completely different approaches relying on whether or not the app owns the net content material. For exterior content material, wrap the WebView in a container and apply insets as padding. For owned content material, both pad the container or inject JavaScript to deal with insets throughout the webpage.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles