PlatformView
是一种特殊的Widget,能在Flutter端显示平台控件(例如 Android View、iOS UiKitView)。需要注意PlatformView只负责NativeView在Flutter端的显示,与Native端的通信等还需借助MethodChannel
等才能完成。
我们知道Flutter在平台的现实是通过平台端(Android的Activity或者iOS的VC)嵌入的FlutterView实现的
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。
iOS的流程也大体相同,只是依赖的平台端技术不同。Android基于VirtualDisplay和Surface,而iOS基于CALayer。
定义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()
}
}
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)
}
}
继承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) {
}
}
在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())
}
}
创建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));
}
}
我们在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);
}
}
在布局中使用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/');
}
}
https://github.com/vitaviva/flutter_platform_sample