【Flutter】使用PlatformView显示WebView

PlatformView是什么?


PlatformView是一种特殊的Widget,能在Flutter端显示平台控件(例如 Android View、iOS UiKitView)。需要注意PlatformView只负责NativeView在Flutter端的显示,与Native端的通信等还需借助MethodChannel等才能完成。

PlatformView的机制

我们知道Flutter在平台的现实是通过平台端(Android的Activity或者iOS的VC)嵌入的FlutterView实现的
【Flutter】使用PlatformView显示WebView_第1张图片
FlutterView内部通过Flutter自渲染引擎绘制内容,已经是平台端整个ViewTree的末端,其内部无法再嵌入任何Native的子View,所以平台端只能设法将内容数据传输到FlutterView并进行绘制。

VirtualDisplay 类似于一个虚拟显示区域,需要结合 DisplayManager 一起调用,一般在副屏显示或者录屏场景下会用到。VirtualDisplay 会将虚拟显示区域的内容渲染在一个 Surface 上。
https://developer.android.com/reference/android/hardware/display/VirtualDisplay

Android端将待显示内容绘制到VirtualDisplay,Flutter Engine 从 VirtualDisplay的Surface中获取纹理,并将其和 Flutter 原有的 UI 渲染树混合,最终在Flutter Widget tree 中以图形方式插入 Android View。
【Flutter】使用PlatformView显示WebView_第2张图片

iOS的流程也大体相同,只是依赖的平台端技术不同。Android基于VirtualDisplay和Surface,而iOS基于CALayer。


PlatformView插件开发步骤


  1. 【Android】 定义PlatformView
  2. 【Android】 定义PlatformViewFactory,用来创建上述View
  3. 【Android】 定义FlutterPlugin,注册上述工厂类
  4. 【Android】 向Flutter端注册上述Plugin
  5. 【Flutter】 创建PlatvormView对应的widget
  6. 【Flutter】 如果有通信需求,在Widget中创建MethodChannel与native通信
  7. 【Flutter】 在layout中使用上述widget显示PlatformView

Android端代码


1. 定义FlutterWebView

定义FlutterWebView,使之继承自io.flutter.plugin.platform.PlatformView

内部持有WebView实例,通过MethodChannel接收Flutter传来的URL并打开网页,注意channel_id的定义要与flutter保持一致。
关于MethodChannel的使用可以参考 Flutter与Native通信之MethodChannel

package com.example.flutter_platform_view_app

import android.content.Context
import android.view.View
import android.webkit.WebView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.platform.PlatformView

class FlutterWebView internal constructor(context: Context?, messenger: BinaryMessenger?, id: Int)
    : PlatformView, MethodCallHandler {

    private val webView: WebView
    private val methodChannel: MethodChannel

    init {
        webView = WebView(context)
        webView.apply {
            settings.apply {
                // enable Javascript
                javaScriptEnabled = true
                setSupportZoom(true)
                builtInZoomControls = true
                displayZoomControls = false // no zoom button
                loadWithOverviewMode = true
                useWideViewPort = true
                domStorageEnabled = true
            }
        }

        methodChannel = MethodChannel(messenger, "plugins.vitaviva.views/webview_$id")
        methodChannel.setMethodCallHandler(this)
    }

    @Override
    override fun getView(): View {
        return webView
    }

    @Override
    override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
        when (methodCall.method) {
            "setUrl" -> setUrl(methodCall, result)
            else -> result.notImplemented()
        }
    }

    private fun setUrl(methodCall: MethodCall, result: MethodChannel.Result) {
        val url = methodCall.arguments as String
        webView.loadUrl(url)
        result.success(null)
    }

    @Override
    override fun dispose() {
        webView.destroy()
    }
}

2. 定义WebViewFactory

WebViewFactory继承自PlatformViewFactory。PlatformView必须通过工厂类创建

package com.example.flutter_platform_view_app

import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

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

    override fun create(context: Context, id: Int, o: Any?): PlatformView {
        return FlutterWebView(context, messenger, id)
    }
}

3. 创建WebViewPlugin

继承io.flutter.embedding.engine.plugins.FlutterPlugin,在onAttachedToEngine回调中注册PlatformViewFactory

package com.example.flutter_platform_view_app

import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin

class WebViewPlugin : FlutterPlugin {

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        flutterPluginBinding.platformViewRegistry
                .registerViewFactory(
                        "plugins.vitaviva.views/webview",
                        WebViewFactory(flutterPluginBinding.binaryMessenger)
                )
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    }
}

4. 注册WebViewPlugin

在Android入口处通过注册插件。通常我们通过MainActivity来显示Flutter,此时可以在MainActivity获得FlutterEngin,并通过其注册插件

package com.example.flutter_platform_view_app

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        flutterEngine.plugins.add(WebViewPlugin())
    }
}

Flutter端代码


5.定义WebView的Widget

创建WebView的widget,build时返回AndroidView。参考AndroidView代码

viewType与我们定义的PlatformView关联,PlatformView创建后会通过onPlatformViewCreated进行回调,可以在里面做一些Flutter端的初始化。

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

typedef void WebViewCreatedCallback(WebViewController controller);

class WebView extends StatefulWidget {
  const WebView({
    Key key,
    this.onWebViewViewCreated,
  }) : super(key: key);

  final WebViewCreatedCallback onWebViewViewCreated;

  @override
  State<StatefulWidget> createState() => _WebViewState();
}

class _WebViewState extends State<WebView> {
  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
        viewType: 'plugins.vitaviva.views/webview',
        onPlatformViewCreated: _onPlatformViewCreated,
      );
    }
    return Text(
        '$defaultTargetPlatform is not supported!');
  }

  void _onPlatformViewCreated(int id) {
    if (widget.onWebViewViewCreated == null)
      return;
    widget.onWebViewViewCreated(new WebViewController._(id));
  }
}

6.使用MethodChannel通信

我们在onPlatformViewCreated中创建一个工具类WebViewController,内部创建MethodChannel用来与Android通信

class WebViewController {
  WebViewController._(int id)
      : _channel = new MethodChannel('plugins.vitaviva.views/webview_$id');

  final MethodChannel _channel;

  Future<void> setUrl(String url) async {
    assert(url != null);
    return _channel.invokeMethod('setUrl', url);
  }
}

7. 显示Widget

在布局中使用WebView的widget,显示PlatformView。同理我们还可以用PlatformView显示android的TextView,最终使用效果如下:

import 'package:flutter/material.dart';
import 'package:flutter_platform_view_app/text_view.dart';
import 'package:flutter_platform_view_app/web_view.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter PlatformView API',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter PlatformView Example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: Column(children: [
          Center(
              child: Container(
                  padding: EdgeInsets.symmetric(vertical: 10.0),
                  width: 200.0,
                  height: 40.0,
                  child: TextView(
                    onTextViewCreated: _onTextViewCreated,
                  )
              )
          ),
          Expanded(
              flex: 3,
              child: WebView(
                    onWebViewViewCreated: _onWebViewCreated,
              )
          )
        ])
    );
  }

  void _onTextViewCreated(TextViewController controller) {
    controller.setText('Android TextView and WebView example.');
  }

  void _onWebViewCreated(WebViewController controller) {
    controller.setUrl('https://www.google.co.jp/');
  }
}

Sample代码

https://github.com/vitaviva/flutter_platform_sample

你可能感兴趣的:(Flutter)