Flutter嵌入原生View,使用原生的WebView

在Flutter开发过程中,我们发现Flutter对于WebView的支持并不太好,而我们原生项目中,使用的腾讯的X5 WebView,所以也想在Flutter中进行嵌入使用。
具体的操作步骤如下

Android端

新建FlutterWebView,继承自PlatformView

class FlutterWebView(
    val context: Context,
    val messenger: BinaryMessenger,
    val viewId: Int,
    val args: Map<String, Any>?
) : PlatformView, MethodChannel.MethodCallHandler {
    private var webView: WebView? = null

    override fun getView(): View {
    	//curAct在Application.ActivityLifecycleCallbacks中进行注入
        val curAct = App.getApp().curActivity
        webView = WebView(curAct)
        //..... 这里可以进行webView的一些配置
        
        if (args != null) {
            val url = args["url"] as String
            webView!!.loadUrl(url)
        } else {
            throw IllegalArgumentException("args is null.")
        }
        return webView!!
    }

    override fun dispose() {
        webView?.destroy()
    }
}

新建FlutterWebViewFactory,继承自PlatformViewFactory

class FlutterWebViewFactory(val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {

    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val flutterView = FlutterWebView(context, messenger, viewId, args as Map<String, Any>?)
        return flutterView
    }
}

新建FlutterWebViewPlugin,继承自FlutterPlugin

class FlutterWebViewPlugin : FlutterPlugin {

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        val messenger: BinaryMessenger = binding.binaryMessenger
        binding
            .platformViewRegistry
            .registerViewFactory(
                "plugins.flutter.test/webview", FlutterWebViewFactory(messenger)
            )
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {

    }

    companion object {
        @JvmStatic
        fun registerWith(registrar: PluginRegistry.Registrar) {
            registrar
                .platformViewRegistry()
                .registerViewFactory(
                    "plugins.flutter.test/webview",
                    FlutterWebViewFactory(registrar.messenger())
                )
        }
    }
}

接着,我们需要注册这个FlutterWebViewPlugin
我们这里使用的是FlutterBoost框架,所以,直接在FlutterBoost初始化的地方进行注册即可

FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
        @Override
        public void pushNativeRoute(FlutterBoostRouteOptions options) {
            //...省略代码...
        }

        @Override
        public void pushFlutterRoute(FlutterBoostRouteOptions options) {
            //...省略代码...
        }
    }, engine -> {
    	//进行FlutterWebViewPlugin的注册
        engine.getPlugins().add(new FlutterWebViewPlugin());       
    });

如果没有使用FlutterBoost,可以在继承自FlutterActivity的代理Activity中进行注册,具体可以看我的另一篇博客 Flutter和Native之间进行通信

Flutter端

新建liu_web_view.dart,相当于封装一个组件库

class LiuWebView extends StatefulWidget {
  final String url;
  final String iOSWebType;

  const LiuWebView(
      {Key? key,
      required this.url,
      this.iOSWebType = "full"})
      : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _LiuWebViewState();
  }
}

class _LiuWebViewState extends State<LiuWebView> {
  late MethodChannel webViewChannel;

  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
          onPlatformViewCreated: (viewId) {
            initWebViewChannel(viewId);
          },
          creationParams: {'url': widget.url},
          creationParamsCodec: StandardMessageCodec(),
          viewType: 'plugins.flutter.test/webview');
    } /*else if (defaultTargetPlatform == TargetPlatform.iOS) {
      //IOS相关的逻辑我们可以先不去实现
      return UiKitView(
        onPlatformViewCreated: (viewId) {
          initWebViewChannel(viewId);
        },
        viewType: 'plugins.flutter.test/webview',
        layoutDirection: TextDirection.ltr,
        creationParams: <String, dynamic>{
          'url': widget.url,
          'type': widget.iOSWebType
        },
        creationParamsCodec: const StandardMessageCodec(),
      );
    }*/ else {
      throw Exception("not impl:" + defaultTargetPlatform.toString());
    }
  }
}

最后,在需要的页面里进行使用即可

class _MyHomePageState extends State<MyHomePage> {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test"),
        ),
        body: Container(
          child: LiuWebView(url: "https://www.baidu.com"),
        ));
  }
}

Flutter和原生View进行双向的交互

如果我们需要将原生WebView的加载状态告知给Flutter,应该怎么做呢 ?
Flutter和原生的通信我们都知道,是使用Channel来进行交互的。同理,这里也是使用Channel,双向交互的话,我们可以选用MethodChannel。

Android端修改FlutterWebView

class FlutterWebView(
    val context: Context,
    val messenger: BinaryMessenger,
    val viewId: Int,
    val args: Map<String, Any>?
) : PlatformView, MethodChannel.MethodCallHandler {
    private var webView: WebView? = null
    private var methodChannel: MethodChannel

    init {
        Log.i(TAGs.FLUTTER_WEB_VIEW, "onPageStarted viewId:$viewId")
        methodChannel.setMethodCallHandler(this)
    }

    override fun getView(): View {
        val curAct = App.getApp().curActivity
        webView = WebView(curAct)

        webView?.setWebViewClient(object:WebViewClient(){
            override fun onPageStarted(
                webView: com.tencent.smtt.sdk.WebView?,
                url: String?,
                favicon: Bitmap?
            ) {
                super.onPageStarted(webView, url, favicon)
                //告知Flutter,WebView页面加载已经开始
                methodChannel.invokeMethod("onLoadStart", url)
            }

            override fun onPageFinished(webView: com.tencent.smtt.sdk.WebView?, url: String?) {
                super.onPageFinished(webView, url)
                //告知Flutter,WebView页面加载已经完毕
                methodChannel.invokeMethod("onLoadStop", url)
            }
        })

        if (args != null) {
            val url = args["url"] as String
            webView!!.loadUrl(url)
        } else {
            throw IllegalArgumentException("args is null.")
        }
        return webView!!
    }

    override fun dispose() {
        webView?.destroy()
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
		//这里可以通过 Flutter传过来的 call.method 来实现 Flutter 对 原生的调用
		if(call.method == "xxxxx"){
			//do something
		}
    }
}

Flutter端修改liu_web_view.dart

class LiuWebView extends StatefulWidget {
  final String url;
  final String iOSWebType;
  final LoadCallback? onLoadStart;
  final LoadCallback? onLoadStop;

  const LiuWebView(
      {Key? key,
      required this.url,
      this.iOSWebType = "full",
      this.onLoadStart,
      this.onLoadStop})
      : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _LiuWebViewState();
  }
}

typedef LoadCallback = void Function(String url);

class _LiuWebViewState extends State<LiuWebView> {
  late MethodChannel webViewChannel;

  void initWebViewChannel(int viewId) {
    webViewChannel = MethodChannel('com.test/webView$viewId');
    webViewChannel.setMethodCallHandler((call) {
      return handleCall(call);
    });
  }

  Future<dynamic> handleCall(MethodCall call) async {
    logDebug(TAGs.WEB_VIEW, "handleCall method:" + call.method);
    if (call.method == "onLoadStart") {
      //收到 原生WebView 开始加载的状态
      String url = call.arguments as String;
      widget.onLoadStart?.call(url);
    } else if (call.method == "onLoadStop") {
      //收到 原生WebView 结束加载的状态
      String url = call.arguments as String;
      widget.onLoadStop?.call(url);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
          onPlatformViewCreated: (viewId) {
            initWebViewChannel(viewId);
          },
          creationParams: {'url': widget.url},
          creationParamsCodec: StandardMessageCodec(),
          viewType: 'plugins.flutter.test/webview');
    } /*
	IOS相关的逻辑我们可以先不去实现
	else if (defaultTargetPlatform == TargetPlatform.iOS) {
      return UiKitView(
        onPlatformViewCreated: (viewId) {
          initWebViewChannel(viewId);
        },
        viewType: 'plugins.flutter.test/webview',
        layoutDirection: TextDirection.ltr,
        creationParams: <String, dynamic>{
          'url': widget.url,
          'type': widget.iOSWebType
        },
        creationParamsCodec: const StandardMessageCodec(),
      );
    }*/ else {
      throw Exception("not impl:" + defaultTargetPlatform.toString());
    }
  }
}

进行使用

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container(
            child: LiuWebView(
                url: "https://www.baidu.com",
                onLoadStart: (url) {
                  showLoading();
                },
                onLoadStop: (url) {
                  dismissLoading();
                }
            ))
    );
  }
}

OK,完工。这样,我们后期如果有更多方法想要让Flutter来调用,或者原生View的状态回调给Flutter,都可以实现了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

氦客

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值