一个功能丰富的跨平台Webview,使用 webview_flutter 用于移动端,以及 iframe 用于Web端。支持JS互操作。
画廊
基本用法
1. 在你的有状态组件中创建一个 WebViewXController
late WebViewXController webviewController;
2. 在build方法中添加WebViewX widget,并设置 onWebViewCreated 回调,以便在webview初始化时获取控制器
WebViewX(
initialContent: '<h2> Hello, world! </h2>',
initialSourceType: SourceType.HTML,
onWebViewCreated: (controller) => webviewController = controller,
...
... other options
);
重要!
如果你需要在webview顶部添加其他widget(例如,在Stack widget中),你必须将这些widget包裹在 WebViewAware widget中。
这在移动端什么也不做,但在Web端,它允许顶部的widget拦截手势。否则,这些widget可能无法点击,或者iframe会表现异常(意外刷新/重载 – 这是一个众所周知的问题)。
另外,如果你在webview顶部添加了widget,包裹了它们,然后发现iframe仍然意外重载,你应该检查是否有其他widget在不知情的情况下覆盖在上面,或者尝试包裹InkWell、GestureRecognizer或Button widget,看看是哪个导致了问题。
3. 通过控制器进行交互(运行 示例应用 查看一些用例)
webviewController.loadContent(
'https://flutterdart.cn',
SourceType.url,
);
webviewController.goBack();
webviewController.goForward();
...
...
功能
注意:有关 EmbeddedJsContent 等更详细的信息,请访问 utils 文件夹中各自的 .dart 文件。
-
Widget属性
| 功能 | 详情 |
|---|---|
String initialContent |
初始webview内容 |
SourceType initialSourceType |
初始webview内容类型(url, urlBypass 或 html) |
String? userAgent |
用户代理 |
double width |
Widget宽度 |
double height |
Widget高度 |
Function(WebViewXController controller)? onWebViewCreated |
webview初始化后执行的回调 |
Set<EmbeddedJsContent> jsContent |
一组EmbeddedJsContent,它是一个定义了一些JavaScript的对象,这些JavaScript将在页面加载后嵌入(查看示例应用) |
Set<DartCallback> dartCallBacks |
一组DartCallback,它是一个定义了一个Dart回调函数的对象,该函数将从JavaScript调用(查看示例应用) |
bool ignoreAllGestures |
一个布尔值,指定widget初始化后是否应忽略所有手势 |
JavascriptMode javascriptMode |
这指定是否允许JavaScript执行(默认允许,要使用上述功能必须允许) |
AutoMediaPlaybackPolicy initialMediaPlaybackPolicy |
这指定在初始化时(即页面加载时)是否允许媒体内容自动播放 |
void Function(String src)? onPageStarted |
页面开始加载时执行的回调(例如,在更改内容后) |
void Function(String src)? onPageFinished |
页面加载完成时执行的回调 |
NavigationDelegate? navigationDelegate |
如果非空,当用户在webview 中点击某些内容时执行的回调(在Web端,目前仅适用于 SourceType.urlBypass) |
void Function(WebResourceError error)? onWebResourceError |
加载资源时发生错误时执行的回调(Web端问题) |
WebSpecificParams webSpecificParams |
这是一个包含Web端特定选项的对象。这些选项目前在移动端不可用(*暂时) |
MobileSpecificParams mobileSpecificParams |
这是一个包含移动端特定选项的对象。这些选项目前在Web端不可用(*暂时) |
-
控制器属性
| 功能 | 用法 |
|---|---|
| 加载允许iframe嵌入的URL | webviewController.loadContent(URL, SourceType.URL) |
| 加载不允许iframe嵌入的URL | webviewController.loadContent(URL, SourceType.URL_BYPASS) |
| 加载不允许iframe嵌入的URL,并带请求头 | webviewController.loadContent(URL, SourceType.URL_BYPASS, headers: {'x-something': 'value'}) |
| 从字符串加载HTML | webviewController.loadContent(HTML, SourceType.HTML) |
| 从资源加载HTML | webviewController.loadContent(HTML, SourceType.HTML, fromAssets: true) |
| 检查是否可以回退历史记录 | webviewController.canGoBack() |
| 回退历史记录 | webviewController.goBack() |
| 检查是否可以前进历史记录 | webviewController.canGoForward() |
| 前进历史记录 | webviewController.goForward() |
| 重新加载当前内容 | webviewController.reload() |
| 检查是否忽略了所有手势 | webviewController.ignoringAllGestures |
| 设置忽略所有手势 | webviewController.setIgnoreAllGestures(value) |
| 评估“原始”JavaScript代码 | webviewController.evalRawJavascript(JS) |
| 在全局上下文中(“页面”)评估“原始”JavaScript代码 | webviewController.evalRawJavascript(JS, inGlobalContext: true) |
| 调用JS方法 | webviewController.callJsMethod(METHOD_NAME, PARAMS_LIST) |
| 检索webview的内容 | webviewController.getContent() |
| 获取X轴滚动位置 | webviewController.getScrollX() |
| 获取Y轴滚动位置 | webviewController.getScrollY() |
在X轴上按 x 滚动,在Y轴上按 y 滚动 |
webviewController.scrollBy(int x, int y) |
精确滚动到位置 (x, y) |
webviewController.scrollTo(int x, int y) |
| 检索内部页面标题 | webviewController.getTitle() |
| 清除缓存 | webviewController.clearCache() |
限制和注意事项
虽然这个包旨在将两全其美结合起来,但Web和移动端之间存在差异。
-
运行和构建
首先,这个包是在默认
web renderer为html的时候开发的。现在(Flutter 2, Dart 2.12),默认的渲染器是canvaskit。根据我的经验,这个包在 canvaskit 上表现有点奇怪,所以你应该使用
html渲染器。为此,你必须运行普通的
flutter run -d chrome命令,并带有--web-renderer html附加参数,如下所示flutter run -d chrome --web-renderer html
用于运行,而
flutter build web --web-renderer html
用于构建。
-
Web和移动端行为差异
请参阅 issues/#27
-
关于Web内容加载
为了使Web版本(iframe)正常工作,我不得不使用 x-frame bypass 的部分代码,以请求一个CORS代理,该代理会移除阻止iframe嵌入的请求头。
这可能看起来像个 hack,而且确实是,但我找不到其他方法让iframe表现得与移动端webview(它是一种实际的浏览器,所以所有东西都可以默认在那里工作)相似。
-
关于Web导航
在Web端,历史导航栈是从头开始构建的,因为我无法正确处理iframe的内部历史。
已知问题和待办事项
-
[ x ] 在Web端,用户代理和请求头仅在使用
SourceType.urlBypass时生效,并且仅在首次使用时有效(view/web.dart) -
[ x ] 在Web端,应该能够将加载
urlBypass时捕获的任何错误发送到Dart回调,然后像移动端一样通过onWebResourceError回调发送(utils/x_frame_options_bypass.dart) -
[ x ] 在Web端,应该能够添加自定义代理列表,而无需JS空值检查的麻烦(
utils/x_frame_options_bypass.dart) -
[ ? ] 最终(如果可能),
WebSpecificParams和MobileSpecificParams的大部分(如果不是全部)属性应该合并,而这两个对象可能会消失 -
[ x ] 在移动端,当在历史记录中前后移动时,控制器的值源类型会不同步。这是因为URL更改尚未被拦截并相应地设置模型(应该不难修复)。
-
在移动端,如果操作失败,控制器的 callJsMethod 不会抛出错误。它只会在控制台中显示错误。
-
添加测试
-
列表已打开,可能还有其他
鸣谢
如果没有以下支持,这个包将无法实现
- webview_flutter 用于移动端版本
- easy_web_view 提供灵感和Web端版本的起点
- pointer_interceptor 用于修复其他widget在其之上时iframe的问题(见上方)
- x-frame-bypass 用于允许iframe绕过网站的X-Frame-Options: deny/same-origin头部,从而允许我们加载任何网页(就像在移动端一样)
- https://cors.bridged.cc/ 用于免费的CORS代理
- https://api.codetabs.com/ 用于免费的CORS代理
- 最后但同样重要的是,http://deversoft.ro(我工作的公司)在我开发过程中给予了我动力



