前言
flutter开发经常会与原生打交道,flutter web也一样,尤其在web开发时,因为flutter web还不成熟,第三方库缺少,很多功能需要依靠web原生来实现,比如音视频,录音等等。用视频举例,需要用html和js来实现一个视频播放器,然后在flutter页面中使用这个播放器,这如何来实现?
flutter使用web原生组件
我们用HtmlElementView来实现,它就是flutter提供的可以在flutter中嵌入html element的widget,我们看如何使用。
先看一个简单的例子:
代码语言:javascript复制import 'dart:html';
import 'dart:js' as js;
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
class WebTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
IFrameElement frame = IFrameElement()
..width = '640'
..height = '360'
..src = 'https://www.baidu.com'
..style.border = 'none';
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => frame);
return HtmlElementView(viewType: 'hello-world-html');
}
}
这个组件就是嵌入了一个IFrameElement,里面加载了一个web页面,然后可以将这个组件放到flutter的页面中,这样就可以在任意位置显示这个web页面。
所以可以看到大致就是三个步骤:
- 创建一个HtmlElement(IFrameElement就是它的子类,另外还有DivElement、ScriptElement等等,后面会提到),将web的内容放入HtmlElement
- 通过ui.platformViewRegistry.registerViewFactory注册HtmlElement
- 使用HtmlElementView,通过viewType加载HtmlElement即可
上面只是直接打开了一个页面,那么如果想使用一个web组件如何处理?
这时候就需要使用到HtmlElement的其他子类,并且可能同时用到多个,如下:
代码语言:javascript复制import 'dart:html';
import 'dart:js' as js;
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
class WebTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
DivElement frame = DivElement();
//设置样式
StyleElement styleElement = StyleElement();
styleElement.type = "text/css";
styleElement.innerHtml = """
html,
body {
height: 100%
}
.view-video {
width: 100%;
height: 100%;
background-color: black;
}
"""; //这里css样式
frame.append(styleElement); //添加到div中
//引入我们写好的web视频播放器
DivElement divElement = DivElement();
divElement.id = "remote_video";
divElement.className = "view-video";
frame.append(divElement);
//添加js代码,初始化之类的
ScriptElement scriptElement = new ScriptElement();
script = """
var rtcParams = {
codec: "h264",
appID: "${config.appId}",
channel: "${config.channel}",
token: "${config.token}",
element: "remote_video"
};
var jsCallMethodLeave = function() {
leave(rtc)
}
function doInit() {
...
}
doInit();
""";
scriptElement.innerHtml = script;
frame.append(scriptElement);
//最后注册使用
ui.platformViewRegistry.registerViewFactory(
'videoplayer',
(int viewId) => frame);
return HtmlElementView(viewType: 'hello-world-html');
}
}
可以看到其实也是那三步,只是第一步创建HtmlElement复杂了一些,我们通过各种HtmlElement的子类组合出来一个而已。
动态创建web组件
上面创建的web组件有一个问题,因为我们的播放器初始化的时候需要一个参数,而其中部分参数是可变的,比如:
代码语言:javascript复制channel: "${config.channel}",
这里的${config.channel}就是使用flutter代码中的config.channel。但是我们使用的时候发现,无论我们怎么重新创建WebTest这个组件(用不同的参数),我们使用的一直都是第一次创建这个组件的参数。也就是说后续的创建其实没有创建而是直接复用?
这个问题就出现在注册上,通过ui.platformViewRegistry.registerViewFactory
注册后,再次注册同样的viewType就不再更新,不是没有创建,而是HtmlElementView使用的一直都是最初注册的那个。
解决这个问题就是使用动态的viewType,比如加入时间戳,如下:
代码语言:javascript复制String _divId = "videoplayer" DateTime.now().toIso8601String();
ui.platformViewRegistry.registerViewFactory(
_divId,
(int viewId) => frame);
return HtmlElementView(viewType: _divId);
这样就会每次使用最新参数创建的组件。
js执行问题
因为注册的问题其实会导致另外一个小问题,就是js的执行。如果viewType是固定的,那么这个web组件其实只初始化一次,所以js代码中的doinit()也只执行一次,无论在新的页面创建新的WebTest组件,最终使用的都是一个HtmlElement,所以如果在doinit()中打印了相关信息,可以看到后面再次创建或使用就不再打印了。
而使用动态viewType就不再有这样的问题,每次都会重新执行js。
交互
这种嵌入的web组件也会有与flutter进行交互的需求。
这个交互其实根flutter与js的交互是一样的,因为这时候的页面里也加载了这个组件的相关js文件(包括我们自己定义的ScriptElement),所以通过js.context.callMethod("xxx");
和js.context["xxx"] = function;
这种方式即可实现交互,比如:
js.context.callMethod("jsCallMethodLeave");
在flutter代码中调用js中的jsCallMethodLeave
函数(前面我们自己定义)。
比如:
代码语言:javascript复制js.context["webEvent"] = webEvent;
在flutter中为js注册一个回调webEvent函数,这样在js中就可以通过window.parent.webEvent(xxx);
这种方式回调到flutter的webEvent函数中,实现了js与flutter的通信。
注意js中同名函数的问题,比如上面我们自己定义的jsCallMethodLeave
就是为了防止与组件自带的js文件中的函数同名而导致调用失败(找不到函数等问题)