关于在Flutter Web中加载html
最近碰到一个需求,需要在Flutter Web项目中加载html。在手机端我们可以使用webview_plugin
插件加载,但是这个插件在web上面是无效的。那么,在Flutter Web中,该如何加载html呢?Flutter为我们提供了一个专门用于web的控件:HtmlElementView
。(这里说点题外话,HtmlElementView的注释文档里面有写,构建html是昂贵操作,如果有等效的Flutter实现可以替换,那么请尽量不要这么搞。)
HtmlElementView的基本使用规则
使用这个控件必须要注意,使用之前需要注册!!!
可以在initState()
的时候注册,当然你也可以在runApp()
的时候就全局注册好(个人不建议):
import 'dart:ui' as ui;
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => HtmlElement();
ui.platformViewRegistry
会报静态错误,不用管,完全不影响你代码跑起来。解释下registerViewFactory()
方法中传的两个参数:第一个个人理解是给你需要插入的html片段取一个id名称,第二个参数则是对应的真正用来加载html的元素。这里的HtmlElement()
是一个基类,实际书写的时候应该传入IFrameElement
、HtmlHtmlElement
、MediaElement
、BodyElement
等等你实际需要的元素。
使用HtmlElementView
时,只需要将已注册元素对应的id传进去就可以了:
HtmlElementView(
viewType: 'hello-world-html',
)
加载URL
加载URL可以使用IFrameElement
(url请写全了,不要漏掉https
):
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => IFrameElement()
..style.border = 'none'
..src = 'https://www.baidu.com');
加载本地html文件
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => IFrameElement()
..style.border = 'none'
..src = '/assets/test2.html');
加载html string
- 加载比较复杂的混合型html(篇幅比较短的)
如果是各种标签混合的html,可以使用HtmlHtmlElement
:
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => HtmlHtmlElement()
..style.border = 'none'
..setInnerHtml(html));
这里要注意有坑,因为实际项目中使用到的html,含有<img>
、<a href>
之类的标签是很正常的,但是如果你按照上面的方式加载,图片、超链接之类的都是没法显示的,因为默认的HtmlHtmlElement
是把这些标签禁用掉的(估计是为了性能吧,毕竟开篇就说了,Flutter其实不希望你直接加载html标签的),你需要手动开启允许加载复杂标签。具体做法是传入一个validator
参数:
final NodeValidator _validator = NodeValidatorBuilder.common()
..allowElement('img', attributes: ['src'], uriPolicy: _AllowUriPolicy())
..allowElement('a', attributes: ['href'], uriPolicy: _AllowUriPolicy());
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => HtmlHtmlElement()
..style.border = 'none'
..setInnerHtml(html,validator: _validator));
class _AllowUriPolicy implements UriPolicy {
@override
bool allowsUri(String uri) {
return true;
}
}
- 加载比较复杂的混合型html(篇幅比较长的)
如果是长篇大论的html,如果你用HtmlHtmlElement
加载,就会发现页面无法滚动。需要通过与ScrollView
组合展示,但是要注意,HtmlElementView
的高度需要计算:
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => HtmlElementView()
..style.border = 'none'
..innerHtml = content);
关于计算:可以通过scrollOffset
获取高度,但是这个高度获取会有一个问题,第一次进入页面时的高度是准确的,但是如果你调整了窗口的大小,高度就会变得不准确了。暂时性的解决办法是监听到窗口大小变化时,强行重新push本页面(在Flutter Web中,调整窗口大小本身就是会重构当前页面的,包括widget和数据都会重构,但是这和重新push似乎是不一样的,会带有某些缓存数据?导致通过scrollOffset
获取的HtmlElementView
高度会无线增大)。如果你有更好的解决方案,欢迎留言告诉我。
万能的IFrameElement
也可以加载混合型html,使用srcdoc
的方式加载,但是IFrameElement如果和ScrollView
组合的话会比较麻烦,这玩意儿高度好像不太好算(希望会算的大佬教教我):
ui.platformViewRegistry.registerViewFactory(
'hello-world-html',
(int viewId) => IFrameElement()
..style.border = 'none'
..srcdoc = content);
- 加载单个标签的html
建议使用轻量型的MediaElement
、BodyElement
、FormElement
、MenuElement
等等。
转载自:https://juejin.cn/post/6844904094725849095