Jetpack Compose'da WebView kullanmak için bunu AndroidView ile sarmalamanız gerekir.
Bu kılavuzda, yaygın kullanım alanları ve bunları Compose'da nasıl destekleyeceğiniz açıklanmaktadır.
WebView'ı AndroidView ile sarmalama
Yazma işleminde WebView kullanmak için AndroidView ile sarmalayın:
@Composable fun SimpleWebView( initialUrl: String, modifier: Modifier = Modifier ) { AndroidView( modifier = modifier.fillMaxSize(), factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = true loadUrl(initialUrl) } } ) }
Bu yöntem, uygulamanızda basit bir URL göstermek için uygundur. Ancak WebView, Android View yaşam döngüsünden ve Compose yaşam döngüsünden ayrı olan karmaşık durum yaşam döngüleriyle ilgilenir. Oluştur'u entegre etmek, zor hatalara yol açan karmaşık WebView senaryolara neden olabilir. Aşağıdaki bölümlerde, bu özellikleri desteklemek için özel işlem gerektirebilecek kullanım alanları açıklanmaktadır.
WebView durumunu kalıcı hale getirme
Compose'da yapılandırma değişikliklerini ve gezinmeyi yönetmek zordur. Bunun nedeni, WebView'nın ana makinesine Activity bağlı eski bir View olması ve örneğinin Activity yaşam döngüsünden daha uzun süre yaşaması önerilmemesidir.
Bu nedenle, WebView durumunu kalıcı hale getirmenin standart yolu, WebView örneklerinin Activity ile birlikte yok edilmesine ve yeniden oluşturulmasına izin vermektir. Bundle kullanarak dahili gezinme geçmişini ve kaydırma durumunu manuel olarak kalıcı hale getirebilirsiniz.
@Composable fun PersistentWebView(url: String) { val webViewStateBundle = rememberSaveable { Bundle() } AndroidView( factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = true // Restore the state and history if (webViewStateBundle.containsKey("WEBVIEW_STATE")) { restoreState(webViewStateBundle.getBundle("WEBVIEW_STATE")!!) } else { loadUrl(url) } } }, onRelease = { releasedWebView -> // Save navigation history before the instance is destroyed val bundle = Bundle() releasedWebView.saveState(bundle) webViewStateBundle.putBundle("WEBVIEW_STATE", bundle) }, modifier = Modifier.fillMaxSize() ) }
Geri gezinmeyi yönetme
WebView'nın gezinme geçmişi olduğunda sistemin geri gitme hareketi, ekrandan çıkmak yerine WebView içinde geriye doğru gezinmelidir.
Sistemin geri gitme etkinliğini yakalamak ve WebView goBack() işlevini çağırmak için Compose BackHandler API'sini kullanın:
// ... @Composable fun BackNavigationDemoScreen(onBack: () -> Unit) { // Hold a reference to the WebView to check its history state var webViewReference by remember { mutableStateOf<WebView?>(null) } // Intercept the system back press if the WebView has history BackHandler(enabled = true) { val webView = webViewReference if (webView != null && webView.canGoBack()) { webView.goBack() // Go back in history } else { onBack() // Exit screen } } Scaffold( topBar = { TopAppBar( title = { Text("Back Navigation Demo") }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } } ) } ) { padding -> Column(modifier = Modifier.fillMaxSize().padding(padding)) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true // Keeps link navigations internal to the WebView instead of opening Chrome webViewClient = WebViewClient() loadUrl("https://developer.android.com") webViewReference = this } }, onRelease = { webViewReference = null } ) } } }
Bu uygulama, tarayıcı tarzı gezinme davranışı sağlar.
İç içe kaydırma
Compose'da WebView kullanılırken iç içe kaydırma kolayca desteklenmez. WebView, LazyColumn gibi kaydırılabilir bir Compose kapsayıcısının içine yerleştirildiğinde WebView tüm kaydırma hareketlerini kullanabilir.
WebView kendi dahili oluşturma motorunu kullandığından, LazyColumn ile iç içe yerleştirme şu anda düzgün çalışmamaktadır.
WebView için resmi iç içe kaydırma desteğinin ilerleme durumunu izlemek üzere bu soruna göz atın.
Uçtan uca düzenler ve pencere yerleştirmeleri
Uçtan uca düzenler kullanılırken WebView içerik, durum çubuğu gibi sistem çubuklarının altında görünebilir. windowInsetsPadding değiştiricisini kullanarak WebView öğesinin tamamını güvenli alana taşıyabilirsiniz:
@Composable fun EdgeToEdgeDemo(url: String) { AndroidView( modifier = Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), factory = { context -> WebView(context).apply { loadUrl(url) } } ) }
İç boşluklar hakkında daha fazla bilgi için WebView'da pencere iç boşluklarını anlama başlıklı makaleyi inceleyin.
Uygulama temasını WebView içeriğiyle senkronize etme
Uygulama açık ve koyu tema arasında geçiş yaptığında, WebView içerik doğru şekilde işlenirse sayfa yeniden yüklenmeden otomatik olarak güncellenebilir.
Web sayfası içeriğinin sahibiyseniz renkleri uygulamanın temasıyla senkronize etmek için prefers-color-scheme medya sorgusunu işleyerek web sayfanızın seçilen temaya uyum sağladığından emin olun.
Açılır listeler ve pop-up'lar gibi yerel öğelerin uygulama temanızı algılayıp eşleştirmesini sağlamak için DayNight stil teması uygulayın.Activity.
<resources> <!-- ... <!-- Use a DayNight theme in your manifest to handle both modes automatically --> <style name="Theme.Webviewdemo.DayNight" parent="Theme.AppCompat.DayNight.NoActionBar" /> </resources>
@Composable fun ThemeSyncDemo(onBack: () -> Unit) { val context = LocalContext.current AndroidView( modifier = Modifier.fillMaxSize(), factory = { _ -> WebView(context).apply { settings.javaScriptEnabled = true webViewClient = WebViewClient() val html = """ <html> <head> // ... @media (prefers-color-scheme: dark) { body { background-color: #212121; color: #ffffff; } select { border-color: #BB86FC; background: #212121; color: #ffffff; } } </style> </head> // ... </html> """.trimIndent() loadDataWithBaseURL(null, html, "text/html", "UTF-8", null) } } ) }
Web sayfasında koyu tema yoksa veya web içeriğinin sahibi siz değilseniz algoritmik karartma, koyu temayı zorunlu kılmaya yardımcı olabilir. Koyu modu olan modern web siteleri bu algoritmayı yoksayar ve bunun yerine kendi yerleşik stillerini kullanır.
Oluşturma'da web izinlerini yönetme
Bir web sayfası donanım veya veri erişimi (ör. kamera, mikrofon ya da konum) istediğinde WebView içinde belirli geri çağırma işlemleri tetiklenir
WebChromeClient. Bu geri çağırmaları işlemeniz ve ilgili Android çalışma zamanı izinlerinin verildiğinden emin olmanız gerekir.
Kamera ve mikrofon izinlerini yönetme
Bir web sayfası kamera veya mikrofon erişimi istediğinde (örneğin, WebRTC veya video kaydı için) WebView çağrısı yapılır WebChromeClient.onPermissionRequest.
Ancak grant() işlevini çağırmadan önce aşağıdaki Android çalışma zamanı izinlerini istemeniz gerekir:
Manifest.permission.CAMERAManifest.permission.RECORD_AUDIO
İlk olarak, WebView için WebView tarafından istenen PermissionRequest izinlerini takip eden bir izin işleyici tanımlayın:
class WebViewPermissionHandler( private val launcher: ManagedActivityResultLauncher<Array<String>, Map<String, Boolean>> ) { var pendingRequest by mutableStateOf<PermissionRequest?>(null) private set fun handleRequest(request: PermissionRequest) { val isTrustedOrigin = request.origin.host == "www.trusted-domain.com" || request.origin.host == "app.local" // Always verify the origin before granting request if (!isTrustedOrigin) { Log.w("WebViewPermission", "Blocked and denied permission request from untrusted origin: ${request.origin.host}") request.deny() return } val androidPermissions = mutableListOf<String>() request.resources.forEach { resource -> when (resource) { PermissionRequest.RESOURCE_VIDEO_CAPTURE -> androidPermissions.add(Manifest.permission.CAMERA) PermissionRequest.RESOURCE_AUDIO_CAPTURE -> androidPermissions.add(Manifest.permission.RECORD_AUDIO) } } // Save the request and launch the Android system dialog pendingRequest = request launcher.launch(androidPermissions.toTypedArray()) } fun onResult(results: Map<String, Boolean>) { val allGranted = results.values.all { it } Log.d("WebViewPermission", "Kotlin: All permissions granted? $allGranted") if (allGranted) { pendingRequest?.grant(arrayOf("/* list of permissions */")) } else { pendingRequest?.deny() } pendingRequest = null } }
Ardından, WebViewPermissionHandler değerini hatırlayan bir composable oluşturun. İzin istemek için rememberLauncherForActivityResult kullanın:
@Composable fun rememberWebViewPermissionHandler(): WebViewPermissionHandler { val handlerState = remember { mutableStateOf<WebViewPermissionHandler?>(null) } val launcher = rememberLauncherForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> handlerState.value?.onResult(results) } return remember { WebViewPermissionHandler(launcher).also { handlerState.value = it } } }
onPermissionRequest geri çağırma işlevinden gelen izni işleyin. Bu işlem, izin başlatıcıyı açar:
@Composable fun WebViewPermissionScreen() { val permissionHandler = rememberWebViewPermissionHandler() AndroidView( factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { // Simply delegate to the handler permissionHandler.handleRequest(request) } } // load a web page that needs permissions } }, modifier = Modifier.fillMaxSize() ) }
Yerleştirilmiş WebView'a alternatif
WebView yerleştirmekten kaçınmayı tercih ederseniz Android, web içeriğini görüntülemek için Chrome Özel Sekmeleri gibi başka seçenekler sunar. Kullanım alanlarınız (ör. göz atma veya kimlik doğrulama) için doğru yaklaşımı nasıl seçeceğinizi anlamak üzere Android uygulamanızda web içeriğini kullanma başlıklı makaleyi inceleyin.