Android WebView 实现打开新标签页

最近接到了个需求,需要让原生的WebView实现打开新标签页的功能。

WebView想要实现打开新标签页,其实就是在WebChromeClientonCreateWindow回调方法中创建一个新的WebView来加载新的网页,下面介绍下如何实现。

WebView支持打开新标签页

1. 修改WebSettings

修改WebSettings,开启支持多窗口。

注意:H5使用window.open()和window.close()来实现开关新标签页,需要开启javaScript。H5使用href+target实现打开新标签页,可以不开启javaScript。

代码如下:

val settings=webView.settings
//javaScript非必须开启
settings.javaScriptEnabled = true
settings.javaScriptCanOpenWindowsAutomatically = true
//开启支持多窗口
settings.setSupportMultipleWindows(true)
复制代码

2. 设置WebChromeClient

创建WebChromeClient并重写onCreateWindowonCloseWindow方法,代码如下:

webView.webChromeClient = object : WebChromeClient() {
    override fun onCreateWindow(view: WebView?, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message?): Boolean {
        //创建新WebView,并使用新WebView来加载新标签页的链接
        resultMsg?.run {
            val webViewTransport = obj as? WebView.WebViewTransport
            webViewTransport?.let { transport ->
                transport.webView = WebView(context)
            }
            sendToTarget()
        }
        return true
    }

    override fun onCloseWindow(window: WebView?) {
        super.onCloseWindow(window)
        //销毁新建的WebView
    }
}
复制代码

3. 完整代码

做了个示例,完整代码如下:

  • 初始页H5
<!DOCTYPE html>
<html lang=zh-CN>
<head>
    <meta charset=utf-8>
    <title>test</title>
    <script>
        function openNewWindow(){
          window.open("file:///android_asset/index_new_tab.html")
        }

        function androidCallJsWithParams(arg){
           document.getElementById("message").innerHTML += (arg);
        }
    </script>
</head>
<body>
<div style="position:relative;left:40px;top:100px">
    <p id='message' style="font-size:24px;position:relative;top:20px">receive:</p>
    <a href="file:///android_asset/index_new_tab.html" target="_blank" style="font-size:24px;position:relative;top:20px">use a tag</a>
    <button type="button" style="width:280px;height:88px;font-size:24px;position:relative;left:20px;top:30px"
            onclick="openNewWindow()">
        use window open
    </button>
</div>
</body>
</html>
复制代码
  • 新标签页H5
<!DOCTYPE html>
<html lang=zh-CN>
<head>
    <meta charset=utf-8>
    <title>test</title>
    <script>
        function closeNewWindow(){
           window.close()
        }

        function androidCallJsWithParams(arg){
           document.getElementById("message").innerHTML += (arg);
        }
    </script>
</head>
<body>
<div style="position:relative;left:40px;top:100px">
    <p id='message' style="font-size:24px;position:relative;top:20px">receive:</p>
    <button type="button" style="width:280px;height:88px;font-size:24px;position:relative;top:30px"
            onclick="closeNewWindow()">
        use window close
    </button>
</div>
</body>
</html>
复制代码
  • WebViewActivity以及布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@+id/web_view_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <androidx.core.widget.ContentLoadingProgressBar
            android:id="@+id/pb_web_load_progress"
            android:layout_width="0dp"
            android:layout_height="4dp"
            android:max="100"
            android:progress="50"
            android:progressDrawable="@drawable/layer_progress"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="52dp"
            android:layout_height="25dp"
            android:layout_marginStart="18dp"
            android:layout_marginTop="21dp"
            android:src="@mipmap/icon_back"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="ContentDescription" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

class WebViewActivity : Activity() {

    private lateinit var layoutWebViewActivityBinding: LayoutWebViewActivityBinding
    private var mainWebView: WebView? = null
    private var newWebView: WebView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        layoutWebViewActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_web_view_activity)
        layoutWebViewActivityBinding.ivBack.setOnClickListener { onBackPressed() }
        mainWebView = WebView(this)
        mainWebView?.let {
            it.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
            initWebViewSetting(it)
            layoutWebViewActivityBinding.webViewContainer.addView(it)
        }
        mainWebView?.loadUrl("file:///android_asset/index_open_tab.html")
    }

    @SuppressLint("JavascriptInterface", "SetJavaScriptEnabled")
    private fun initWebViewSetting(webView: WebView?) {
        webView?.run {
            settings.cacheMode = WebSettings.LOAD_DEFAULT
            settings.domStorageEnabled = true
            settings.allowContentAccess = true
            settings.allowFileAccess = true
            settings.useWideViewPort = true
            settings.loadWithOverviewMode = true
            settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
            
            settings.javaScriptEnabled = true
            settings.javaScriptCanOpenWindowsAutomatically = true
            //开启支持多窗口
            settings.setSupportMultipleWindows(true)

            webChromeClient = object : WebChromeClient() {
                override fun onProgressChanged(view: WebView, newProgress: Int) {
                    super.onProgressChanged(view, newProgress)
                    layoutWebViewActivityBinding.pbWebLoadProgress.run {
                        post { progress = newProgress }
                        if (newProgress >= 100 && visibility == View.VISIBLE) {
                            postDelayed({ visibility = View.GONE }, 500)
                        }
                    }
                }

                override fun onCreateWindow(view: WebView?, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message?): Boolean {
                    newWebView = WebView(this@WebViewActivity)
                    newWebView?.let { newWeb ->
                        newWeb.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
                        initWebViewSetting(newWeb)
                        layoutWebViewActivityBinding.webViewContainer.addView(newWeb)
                        resultMsg?.run {
                            val webViewTransport = obj as? WebView.WebViewTransport
                            webViewTransport?.let { transport ->
                                transport.webView = newWeb
                            }
                            sendToTarget()
                        }
                    }
                    return true
                }

                override fun onCloseWindow(window: WebView?) {
                    //销毁新建的WebView
                    destroyNewWebView()
                    super.onCloseWindow(window)
                }
            }
            webViewClient = object : WebViewClient() {
                override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                    super.onPageStarted(view, url, favicon)
                    layoutWebViewActivityBinding.pbWebLoadProgress.run {
                        post { visibility = View.VISIBLE }
                    }
                }

                override fun onPageFinished(view: WebView?, url: String?) {
                    super.onPageFinished(view, url)
                    //将WebView信息传到H5页面中 
                    val message="mainWeb:$mainWebView||newWeb:$newWebView||currentWeb:$view"
                    view?.loadUrl("javascript:androidCallJsWithParams(\"$message\")")
                }
            }
        }
    }

    override fun onDestroy() {
        destroyMainWebView()
        destroyNewWebView()
        super.onDestroy()
    }

    override fun onBackPressed() {
        when {
            newWebView?.canGoBack() == true -> newWebView?.goBack()
            newWebView != null -> destroyNewWebView()
            mainWebView?.canGoBack() == true -> mainWebView?.goBack()
            else -> super.onBackPressed()
        }
    }

    private fun destroyMainWebView() {
        mainWebView?.run {
            Log.i(TAG, "destroy mainWeb webView:$this")
            clearHistory()
            loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
            layoutWebViewActivityBinding.webViewContainer.removeView(this)
            destroy()
        }
        mainWebView = null
    }

    private fun destroyNewWebView() {
        newWebView?.run {
            Log.i(TAG, "destroy newWeb webView:$this")
            clearHistory()
            loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
            layoutWebViewActivityBinding.webViewContainer.removeView(this)
            destroy()
        }
        newWebView = null
    }
}
复制代码

实现效果如图:

2c7afe8bc02b6c40d5111211ef1e7293_.gif

分类:
Android
标签: