在 Flutter 中使用 webview_flutter 4.0 | js 交互
大家好,我是 17。
已经有很多关于 Flutter WebView 的文章了,为什么还要写一篇。两个原因:
- Flutter WebView 是 Flutter 开发的必备技能
- 现有的文章都是关于老版本的,新版本 4.x 有了重要变化,基于 3.x 的代码很多要重写。
WebView 的文章分两篇
- 在 Flutter 中使用 webview_flutter 4.0 | js 交互
- Flutter WebView 性能优化,让 h5 像原生页面一样优秀
本篇讲 js 交互。首先了解下 4.0 有哪些重大变化。
- 最大的变化就是 WebView 类已被删除,其功能已拆分为 WebViewController 和 WebViewWidget。让我们可以提前初始化 WebViewController。
- Android 的 PlatformView 的实现目前不再可配置。它在版本 23+ 上使用 Texture Layer Hybrid Compositiond,在版本 19-23 回退到 Hybrid Composition。
第 2 条的变化让我们不需要再写判断 android 的代码了。
还有 api 的变化。总的来说,让我们的编码更加容易了。
写本文的时候,Flutter WebView 的版本是 4.0.2
环境准备
虽然文档上写的是支持 addroid SDK 19+ or 20+, 但我们最好写 21 或更高,不是说会影响 Flutter WebView 的使用,而是太低了会影响其它插件的使用。如果能写 23 就更好了,这样可以用 Texture Layer Hybrid Compositiond 了。
android {defaultConfig {minSdkVersion 21}
}
iOS 支持 9.0 以上,新版本的 flutter 默认配置是 ios 11.0 ,所以我们按 Flutter 默认的配置就好。
安装 webview_flutter
flutter pub add webview_flutter
最简示例
一般举例都是先发一个 hello world,咱们也发一个最简单的,先跑起来。
完整代码,贴到 main.dart 就能运行
- 引用 webview_flutter 插件
- 创建 controller
- 用 WebViewWidget 展示内容
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';const htmlString = '''
<!DOCTYPE html>
<head>
<title>webview demo | IAM17</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no,viewport-fit=cover" />
<style>
*{margin:0;padding:0;
}
body{background:#BBDFFC; display:flex;justify-content:center;align-items:center;height:100px;color:#C45F84;font-size:20px;
}
</style>
</head>
<html>
<body>
<div >大家好,我是 17</div>
</body>
</html>
''';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: Scaffold(body: SafeArea(child: MyWebView()),));}
}class MyWebView extends StatefulWidget {const MyWebView({super.key});@overrideState<MyWebView> createState() => _MyWebViewState();
}class _MyWebViewState extends State<MyWebView> {late final WebViewController controller;double height = 0;@overridevoid initState() {controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted)..loadHtmlString(htmlString);super.initState();}@overrideWidget build(BuildContext context) {return Column(children: [Expanded(child: WebViewWidget(controller: controller))],);}
}
执行代码,你将看到如下内容
WebView 内容的可以通过网址获取,但这样不方便演示各种效果,所以直接用 htmlString 替代了,效果是一样的。
默认情况下 javascript 是被禁用的。必须手动开启 setJavaScriptMode(JavaScriptMode.unrestricted)
,否则对于绝大多数的网页都没法用了。
WebView 的小大
WebViewWidget 会尝试让自己获得最大高度和最大宽度,所以 WebView 必须放在有限宽度和有限高度的 Widget 中。一般会用 SizedBox 这样的容器把 WebView 包起来。但是 WebView 内容的高度是未知的,要如何设置 SizedBox 的 height 呢?
一种方案是 height 采用固定高度,如果 WebView 内容过多,可以用上下滑动的方式来查看所有内容。如果 WebView 的内容高度是变化的,用固定高度可能会产生大块空白,这个时候应该把 height 设置成 WebView 内容的高度。
那么问题来了,如何获得 WebView 内容的高度?最理想的情况是网页是自己能控制的,让网页自己报告高度。
网页自己报告高度
在 htmlString 中 增加 js
<body>
<div class="content">大家好,我是 17</div>
<script>const resizeObserver = new ResizeObserver(entries =>Report.postMessage(document.scrollingElement.scrollHeight))resizeObserver.observe(document.body)
</script>
</body>
如果WebView 不支持 ResizeObserver 可以直接在合适的时机调用 Report.postMessage(document.scrollingElement.scrollHeight))
dart 代码中
- 增加一个变量 height ,初始值为 0。
- 增加 ScriptChannel,注意名字和前面 script 中的名字必须一样,本例中名字叫 Report
- 用 SizedBox 替换 Expanded,限定 WebViewWidget 的高度。
class _MyWebViewState extends State<MyWebView> {late final WebViewController controller;double height = 0;void initState() {controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted)..addJavaScriptChannel('Report', onMessageReceived: (message) {setState(() {height = double.parse(message.message);});})..loadHtmlString(htmlString);super.initState();}Widget build(BuildContext context) {return Column(children: [SizedBox(height: height, child: WebViewWidget(controller: controller)),],);}
}
修改 html 代码中的 body 的样式 height:100px 为 height:200px;
,重新运行代码(restart,hot reload 不生效 ),发现 SizedBox 也变为 200px 高了。
无法修改页面
如果页面我们无权修改也没有办法协调修改,那就只能通过注入 js 方式获取了。
如果页面的高度只由静态 css 决定,可以简单的加一个小延时,直接获取高度即可。
controller.setNavigationDelegate(NavigationDelegate(onPageFinished: (url) async {await Future.delayed(Duration(milliseconds: 50));var message = await controller.runJavaScriptReturningResult('document.scrollingElement.scrollHeight');setState(() {height =double.parse(message.toString());});},));
如果页面加载完成后 js 又对页面进行了修改,这个时间就很难预估了。js 可以随时修改页面,导致高度改变,所以要想时时跟踪页面高度,只能靠监听。如果 webview 不支持 ResizeObserver,还可以用 setInterval。
void initState() {controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted)..addJavaScriptChannel('Report', onMessageReceived: (message) {var msgHeight = double.parse(message.message);setState(() {height = msgHeight;});})..setNavigationDelegate(NavigationDelegate(onPageFinished: (url) async {// 注入 jscontroller.runJavaScript('''const resizeObserver = new ResizeObserver(entries =>Report.postMessage(document.scrollingElement.scrollHeight))resizeObserver.observe(document.body)''');},))..loadHtmlString(htmlString);super.initState();}
必须等到页面加载完成后再注入 js,否则页面文档还不存在,往哪里注入啊。
因为代码都在 dart 这边,免去了和页面开发沟通的成本。既使 WebView 加载的页面中可能还有链接,跳到另一个地址,js 注入的代码依然有效!
页面的高度可能会在很短时间内连续变化,我们可以只对最后一次的高度变化做更新,用 Timer 可以做到。页面高度要限制一个最大值,否则超出最大允许的高度就报错了。
可能你会觉得既然注入的方式这么多优点,不需要页面报告那种方式了,都用这种注入的方式就可以了。实际上每种方式都有它的利弊,不然我就不会介绍了。页面报告的方式在于灵活,想什么时候报告就什么时候报告,页面高度变化了,也可以不报告。在页面没有内容的时候可以先报告一个预估的高度,会让页面避免从 0 开始突然变高。尽量把主动权交给页面,因为页面是可以随时修改的,app 不能!
在网页中调用 Flutter 页面
拦截 url
url 以 /android 结尾时,跳到对应的原生页面。否则继续原来的请求。
onNavigationRequest: (request) {if (request.url.endsWith('/android')) {// 跳到原生页面return NavigationDecision.prevent;} else {// 继续原来的请求return NavigationDecision.navigate;}},
触发方式有两种
- 用 A 标签
<a href='/ios'>跳到 Flutter 页面</a>
- 用 js 跳转
window.location.href='完整页面地址'
用 js 跳转的地址一定是完整的页面地址。比如这样写都是可以的
https://juejin.cn
aa:/bb
schema 可以自定义,但不能没有。这样写是无效的 /android
js 调用 JavaScriptChannel 定义的方法
先定义跳转的通道对象为 Jump
void initState() {controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted)..addJavaScriptChannel('Jump', onMessageReceived: (message) {//根据 message 信息跳转})..loadHtmlString(htmlString);super.initState();}
在页面中执行 Jump.postMessage('video');
实际上,flutter 拿到页面传过来的信息后,除了可以跳转到 flutter 页面,还可以执行其它功能,比如调取相机。
总结
通过两个示例演示了页面与 flutter 通信的 3 种方式
- flutter 拦截 url
- flutter 设置 JavaScriptChannel
- flutter 向页面注入 js
向页面注入 js 需要等页面加载完成后再注入。注入 js 的能力非常强大的。几乎可以对页面做任意修改。比如
- 删除页面中不想要的部分
- 修改页面的样式
- 增加页面的功能,比如给页面增加一个按钮,点按钮跳到原生页面,就好像原来的页面就有这个功能一样。
删除页面中不想要的部分,这是有实际意义的。页面都会有页头,这可能和 app 的头部冲突。有了注入 js 这个利器,可以在不修改页面的情况下,直接在 app 中不显示页头。
修改页面样式,这个你懂的,既然能注入 js ,也就是能注入 css 了。相比于直接用 js 修改页面样式,注入 css 的方式更加容易维护。
当然了,凡事有利有弊,不要滥用这个功能。在 app 单方面修改页面,将来页面修改的时候可能会翻车,即使做好沟通,也会给页面开发造成限制或麻烦,所以如何做一定要权衡各方面的得失。
app 不像页面那样可以随时修改,所以要优先考虑让页面实现功能,尽量把控制权交给页面(说两遍了,因为很重要)。js 注入这种操作不是万不得已不要做,把它做为最后的选项。
最后说一点,示例中为了方便演示用 loadHtmlString,实际应用中一般是用 loadRequest 加载网址。
loadHtmlString(htmlString) loadRequest(Uri.parse('https://juejin.cn'))
本文到这里就结束了。谢谢观看!
相关文章:
在 Flutter 中使用 webview_flutter 4.0 | js 交互
大家好,我是 17。 已经有很多关于 Flutter WebView 的文章了,为什么还要写一篇。两个原因: Flutter WebView 是 Flutter 开发的必备技能现有的文章都是关于老版本的,新版本 4.x 有了重要变化,基于 3.x 的代码很多要重…...

嵌入式ARM工业边缘计算机BL302的CAN总线接口如何设置?
CAN 接口如图所示,输入如下命令: ifconfig -a //查看所有网卡 如果 FlexCAN 驱动工作正常的话就会看到 CAN 对应的网卡接口,如图。从图中可 以看出,有一个名为“can0”的网卡,这个就是 BL302 板上的 CAN1 接口对应的 c…...

Win11系统如何安装Ubuntu20.04(WSL版本)并安装docker
终于还是下定决心去换电脑了……这次采用轻量级的WSL,发现虽然没有占内存的GUI界面,但是编码和阅读文档还是非常nice的 1、首先开启Win11的虚拟机服务 2、下载你期望的Ubuntu服务器(这里以20.04为例) 安装成功后,发现…...

Elasticsearch和Solr的区别
背景:它们都是基于Lucene搜索服务器基础之上开发,一款优秀的,高性能的企业级搜索服务器。(是因为他们都是基于分词技术构建的倒排索引的方式进行查询)开发语言:java语言开发诞生时间:Solr2004年…...
如何在北京买房
首先我陈述一点,如果为了买房后再卖掉赚取差价,我这篇文章也许不适合,我这篇文章为整体愿景的发展而设计,为可操作房产的买卖而操作。 买房的愿景: 首先,我们要以一种心态来买房。那就是以始为终的态度&am…...

使用Proxifier+burp抓包总结
一、微信小程序&网页抓包 1. Proxifier简介 Proxifier是一款功能非常强大的socks5客户端,可以让不支持通过代理服务器工作的网络程序能通过HTTPS或SOCKS代理或代理链。 2. 使用Proxifier代理抓包 原理:让微信相关流量先走127.0.0.1:80到burp。具体…...
安装华为aab包的处理方式
1、转换 aab包 为 apks 说明: 1、bundletool-all-1.11.2.jar 转换文件的工具 2、a.aab aab源文件 3、xxx.apks 导入的文件以及路径(例如:D:\Android\xxx.apks) 4、–ksxxxx.jks 该aab打包所需的jsk文件 5、三条命令为 jsk打包所…...
Word处理控件Aspose.Words功能演示:使用 C++ 将 RTF 文档转换为 PDF
Aspose.Words 是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和打印文档,而无需在跨平台应用程序中直接使用Microsoft Word。此外,API支持所有流行的Word处理文件…...

【Java|多线程与高并发】进程与线程的区别与联系
文章目录什么是进程什么是线程上下文切换多线程一定比串行执行快吗进程与线程的区别与联系什么是进程 进程的定义:进程是正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说CPU(寄存器),IO,内存&a…...
K8s手工创建kubeconfig
我们通过 kubectl 命令行连接 k8s apiserver 时需要依赖 kubeconfig 文件。 kubeconfig 文件通常包含了 context(上下文)列表,每个 context 又会引用 cluster 和 user,最后通过 current-context 指定当前 kubeconfig 使用哪个 con…...
【SQL开发实战技巧】系列(十七):时间类型操作(下):确定两个日期之间的工作天数、计算—年中周内各日期出现次数、确定当前记录和下一条记录之间相差的天数
系列文章目录 【SQL开发实战技巧】系列(一):关于SQL不得不说的那些事 【SQL开发实战技巧】系列(二):简单单表查询 【SQL开发实战技巧】系列(三):SQL排序的那些事 【SQL开发实战技巧…...

代码随想录算法训练营第二十八天 | 491.递增子序列,46.全排列,47.全排列 II
一、参考资料递增子序列题目链接/文章讲解:https://programmercarl.com/0491.%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.html 视频讲解:https://www.bilibili.com/video/BV1EG4y1h78v 全排列题目链接/文章讲解:https://programmercarl.…...

使用 Three.js 后处理的粗略铅笔画效果
本文使用Three.js的后处理创建粗略的铅笔画效果。我们将完成创建自定义后处理渲染通道、在 WebGL中实现边缘检测、将法线缓冲区重新渲染到渲染目标以及使用生成和导入的纹理调整最终结果的步骤。翻译自Codrops,有改动。 Three.js 中的后处理 Three.js中的后处理是一…...

推荐一些不常见的搜索引擎
5.雅虎网来自 Yahoo.com 的屏幕截图,2023 年 2 月截至 2022 年 1 月,Yahoo.com(Verizon Media)的搜索市场份额为 11.2%。雅虎的优势在于多元化,除搜索外还提供电子邮件、新闻、金融等服务。二十多年来,雅虎…...

RabbitMQ工作模式
目录1.Work queues工作队列模式1.1 模式说明1.2 代码1.3 测试1.4 小结2.订阅模式类型3.Publish/Subscribe发布与订阅模式3.1 模式说明3.2 代码3.3 测试3.4 小结4.Routing路由模式4.1 模式说明4.2 代码4.3 测试4.4 小结5.Topics通配符模式5.1 模式说明5.2 代码5.3 测试5.4 小结6…...

机器学习在预测脊髓型颈椎病中的应用:一项28名参与者的事后初步研究
机器学习在预测脊髓型颈椎病中的应用:一项28名参与者的事后初步研究 Machine Learning for the Prediction of Cervical Spondylotic Myelopathy: A Post Hoc Pilot Study of 28 Participants 简单说:训练了两个模型:1)预测脊髓型颈椎病诊断࿰…...

【智能计算数学】微积分
高数问题解决流程引例:回归回归引例:分类分类线性可分FLD线性不可分智能计算讨论范围下降法为什么要用下降法?- 解析解很难写出公式或很复杂难计算有哪些常用的下降法?- 梯度下降&高斯-牛顿法梯度下降(Gradient De…...
win10+RTX4070ti+libtorch部署
环境cuda 11.7、cudnn8.6.0、libtorch1.13.1cu117 注意: 1)libtorch官网进不去的可直接下载 Release version https://download.pytorch.org/libtorch/cu117/libtorch-win-shared-with-deps-1.13.1%2Bcu117.zip Debug version https://download.pytorch.…...
【Python百日进阶-Web开发-Vue3】Day518 - Vue+ts后台项目5:用户列表
文章目录 一、获取用户列表的数据1.1 定义用户列表和角色列表的接口src/request/api.ts1.2 获取用户列表数据src/views/UserView.vue二、定义用户列表数据类型2.1 src/type/user.ts三、展示用户列表内容3.1 element-plus中的Select 选择器3.2 element-plus中的表格插槽3.3 展示…...

Linux内核转储---kdump原理梳理
文章目录Kexec和Kdump设计的区别kexeckdumpKdump的执行流程kexec的实现用户空间kexec内核空间vmcoreKdump的实现可以分为两部分:内核和用户工具。内核提供机制,用户工具在这些机制上实现各种转储策略,内核机制对用户工具的接口是一个系统调用…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
2024年初,人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目(一款融合大型语言模型能力的云端AI编程IDE)时,技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力,TRAE在WayToAGI等…...