# flutter_fai_webview **Repository Path**: android.long/flutter_fai_webview ## Basic Information - **Project Name**: flutter_fai_webview - **Description**: Flutter WebView 插件 - **Primary Language**: Dart - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-09-22 - **Last Updated**: 2026-01-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Flutter 加载 Html Flutter与JS的双向调用 Flutter是谷歌推出的最新的移动开发框架。 [微信公众号 biglead ] *** 大家如果有问题可以随时关注公众号联系作者,沟通解决需求。 >2025年11月15日 更新 1.3.5 iOS 支持设置 webView 加载进度的回调 >2025年11月14日 更新 1.3.4 Andorid 支持设置 webView 加载进度的回调 >2023年4月30日 更新 1.3.2 Andorid 支持设置webView的缓存模式 ```dart enum WebViewCacheMode { LOAD_CACHE_ONLY, // 不发网络请求资源,只读取缓存。 LOAD_DEFAULT, //根据cache-control或者Last-Modified决定是否从网络上取数据。默认采用该方案 LOAD_NO_CACHE, //不使用缓存,只从网络获取数据。 LOAD_CACHE_ELSE_NETWORK //只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。本地没有缓存时才从网络上获取。 } ``` * 2020-10-28 更新[1.1.4 版本 处理 Flutter与Html的混合页面加载](https://biglead.blog.csdn.net/article/details/109340398) * 2020-10-27 更新 [1.1.3 版本](https://pub.flutter-io.cn/packages/flutter_fai_webview/install) 优化处理在 Android平台加载显示短暂的黑屏问题 * 2020-7-26 更新支持 Android iOS 平台向前或者是后退浏览历史功能 请查看本文章第四小节 * 2020-8-12 修复在iphone8早JS调用Flutter失效问题[版本1.1.1](https://pub.flutter-io.cn/packages/flutter_fai_webview/install) *** > 本篇文章讲述的内容可以用来加载 Html 页面,以实现 Android 中 WebView 或者 是 iOS 中的 UIWebView 中的功能。 **Flutter_Fai_Webview 插件可实现的功能:** * 同时适配于 Android Ios 两个平台 * 通过 url 来加载渲染一个Html 页面 * 加载 Html 文本数据 如 ``` .... ```等 * 加载 Html 标签数据 如 ```

...

``` * 实现 WebView 加载完成后,自动测量 WebView 的高度,并回调 Flutter * 实现 WebView 加载完成监听 * 实现 WebView 上下滑动、滑动到顶部兼听、滑动到底部兼听并回调 Flutter * 实现 兼听 WebView 输出日志并将日志回调 Flutter * 实现 为 Html 页面中所有的图片添加点击事件 并回调 Flutter * 实现Html与Flutter的JS双向互调 * 实现打开相机相册的功能 * 实现回退历史浏览记录的功能 * 实现监听Html中图片的点击事件回调 * 刷新Html页面的加载 *** **Flutter中可用于来加载 Html 页面的插件 ,** * flutter_WebView_plugin * webView_flutter * flutter_inappbrowser * html * flutter_html * flutter_html_view 这些多多少满足不了我项目中的需求,所以花了几天时间开发了 Flutter_Fai_Webview 插件,可实现 Android 中 WebView 或者 是 iOS 中的 UIWebView 中的功能,因为 Flutter_Fai_Webview 插件本质上是通过 PlatformView 功能将原生的 View 嵌套在 Flutter 中。 [插件源码在这里](https://github.com/zhaolongs/Flutter_Fai_Webview) **开发插件要具备的知识:** * Flutter 与 原生 Android iOS 双向通信 [Flutter通过MethodChannel实现Flutter 与Android iOS 的双向通信](https://blog.csdn.net/zl18603543572/article/details/96049359) [Flutter通过BasicMessageChannel实现Flutter 与Android iOS 的双向通信](https://blog.csdn.net/zl18603543572/article/details/96043692) * Flutter 中内嵌 Android iOS 原生View的编程基础 [flutter调用android 原生TextView ](https://blog.csdn.net/zl18603543572/article/details/95983215) [flutter调用ios 原生View ](https://blog.csdn.net/zl18603543572/article/details/96125516) * 最重要的一点是 具备 Android iOS 原生语言的开发能力 *** #### 开始使用 #### 1 基本使用说明 ##### 1.1 Flutter 项目中 pubspec.xml 文件中 配置插件 pub方式依赖:[点击这里查看最新版本](https://pub.flutter-io.cn/packages/flutter_fai_webview) ```java dependencies: flutter_fai_webview: ^1.3.5 ``` git方式依赖: ```dart flutter_fai_webview: git: url: https://gitee.com/android.long/flutter_fai_webview.git ref: master ``` ##### 1.2 在使用到 WebView 页面中 引入头文件 ```dart import 'package:flutter_fai_webview/flutter_fai_webview.dart'; ``` ##### 1.3 通过url加载网页 如这里使用到的地址: ```dart String htmlUrl = "https://biglead.blog.csdn.net/?type=blog"; ``` 然后使用FaiWebViewWidget来加载显示这个h5链接,代码如下: ```dart // 页面 String htmlUrl = "https://biglead.blog.csdn.net/?type=blog"; ///通过url加载页面 FaiWebViewItemWidget buildFaiWebViewWidget() { return FaiWebViewItemWidget( //webview 加载网页链接 url: htmlUrl, //webview 加载信息回调 callback: callBack, //控制器 controller: _faiWebViewController, //输出日志 isLog: true, ); } ``` 其中callBack是一个回调,如webview的加载完成、向上滑动、向下滑动等等,代码如下: ```dart //当前加载进度 double _progress = 0.0; ///加载 Html 的回调 ///[code]消息类型标识 ///[msg] 消息内容 ///[content] 回传的参数 void callBack(int? code, String? msg, content) { debugPrint('接收到回调信息 $code $content'); if (code == 20001) { //加载进度更新 0~100 int progress = content; //0~1.0 _progress = progress * 1.0 / 100; setState(() {}); return; } //加载页面完成后 对页面重新测量的回调 //这里没有使用到 //当FaiWebViewWidget 被嵌套在可滑动的 widget 中,必须设置 FaiWebViewWidget 的高度 //设置 FaiWebViewWidget 的高度 可通过在 FaiWebViewWidget 嵌套一层 Container 或者 SizeBox if (code == 201) { //页面加载完成后 测量的 WebView 高度 double webViewHeight = content; print("webViewHeight " + webViewHeight.toString()); } else if (code == 202) { // Html 页面中 Js 的回调 // Html 页面中的开发需要使用 Js 调用 【 Android 中 使用 controll.otherJsMethodCall( json )】 【iOS中 直接调用 otherJsMethodCall( json ) 】 // 在 Flutter 中解析 json 然后加载不同的功能 String jsJson = content; //解析一下JSON Map json = jsonDecode(jsJson); //当然这里是测试使用的数据 String name = json['name']; int age = json['age']; }else { //其他回调 } setState(() { message = "回调:code[" + code.toString() + "]; msg[" + msg.toString() + "]"; }); } ``` ##### 1.4 关于回调中的code取值说明如下 | code值 | 简述| |-------|--| | 201 |测量webview 成功 | 202 |JS调用 | 203 |图片点击回调 | 301 |滑动到顶部 | 302 |向下滑动 | 303 |向上滑动 | 304 |滑动到底部 | 401 |webview 开始加载 | 402 |webview 加载完成 | 403 |webview html中日志输出 | 404 |webview 加载出错 | 501 |webview 弹框回调 | 1000 |操作失败 | 20001 |加载进度更新 | 405 |ssl证书相关错误 | 406 |http 加载错误 加载Html的过程中会实时回调Flutter,详细说明如下: ``` /** * code 原生 Android iOS 回调 Flutter 的消息类型标识 * message 消息类型日志 * content 回调的基本数据 */ Function(int code, String message, dynamic content) callback; ``` 详细说明 ```java //当前点击的图片 URL String imageUrl = null; //是否显示浮动按钮 bool isShowFloat = false; /** * code 当前点击图片的 位置 * url 当前点击图片对应的 链接 * images 当前 Html 页面中所有的图片集合 */ void imageCallBack(int code, String url, List images) { imageUrl = url; setState(() {}); } void callBack(int ?code, String ?msg, content) { String call = "回调 code:" + code.toString() + " msg:" + msg.toString() + " content:" + content.toString(); if (code == 201) { //加载页面完成后 对页面重新测量的回调 //这里没有使用到 //当FaiWebViewWidget 被嵌套在可滑动的 widget 中,必须设置 FaiWebViewWidget 的高度 //设置 FaiWebViewWidget 的高度 可通过在 FaiWebViewWidget 嵌套一层 Container 或者 SizeBox webViewHeight = content; } else if (code == 202) { // Html 页面中 Js 的回调 // Html 页面中的开发需要使用 Js 调用 【 Android 中 使用 controll.otherJsMethodCall( json )】 【iOS中 直接调用 otherJsMethodCall( json ) 】 // 在 Flutter 中解析 json 然后加载不同的功能 String jsJson = content; } else if (code == 203) { // 为 Html 页面中的图片添加 点击事件后,点击图片会回调此方法 // content 为当前点击图片的 地址 // 实现更多功能 比如 一个 Html 页面中 有5张图片,点击放大查看并可右右滑动 // 这个功能可以在 imageCallBack 回调中处理 } else if (code == 301) { //当 WebView 滑动到顶部的回调 } else if (code == 302) { //当 WebView 开始向下滑动时的回调 //隐藏按钮 isShowFloat = true; } else if (code == 303) { //当 WebView 开始向上滑动时的回调 //显示按钮 isShowFloat = false; } else if (code == 304) { //当 WebView 滑动到底部的回调 } else if (code == 401) { //当 WebView 开始加载的回调 } else if (code == 402) { //当 WebView 加载完成的回调 } else if (code == 403) { // WebView 中 Html中日志输出回调 } else if (code == 401) { // WebView 加载 Html 页面出错的回调 } else if (code == 501) { // 当 Html 页面中有 Alert 弹框弹出时 回调消息 } else if (code == 1000) { // 操作失败 例如 空指针异常 等等 } else { //其他回调 } setState(() { message = call; }); } ``` *** ![公众号 我的大前端生涯](https://img-blog.csdnimg.cn/20200620175409480.gif) *** #### 2 Flutter 加载页面 ##### 2.1 通过 url 加载 Html 页面 在上述1.3 通过url加载网页已进行过描述 ##### 2.2 通过 Html Data 加载 String类型的Html 页面 加载String类型的Html页面,一般先是将String类型的Html代码加载到内容中,如通过网络请求接口获取的,或者是在页面中定义好的代码如下: ```java String htmlBlockData = "

加载中

"; ``` 或者是放在assets目录的静态Html,那么就需要先读取静态资源目录下的Html文件 ,代码如下: ```java ///读取静态Html Future loadingLocalAsset() async { ///加载 String htmlData = await rootBundle.loadString('assets/html/test.html'); print("加载数据完成 $htmlData"); return htmlData; } ``` 当然你需要配制好目录依赖,如我这里的example中的html文件配置代码如下: ```java assets: - assets/html/ ``` 对应的文件目录如下所示: ![](https://img-blog.csdnimg.cn/20200714213210344.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psMTg2MDM1NDM1NzI=,size_16,color_FFFFFF,t_70) 然后就是使用FaiWebViewWidget来渲染Html页面了,如果是直接显示已加载好的String类型的Html,那么直接配置FaiWebViewWidget中的htmlBlockData就可以,如果是使用异步加载assets目录下的静态Html,那么可以结合FutureBuilder组件来实现加载,代码如下: ```java ///异步加载静态资源目录下的Html FutureBuilder buildFutureBuilder() { return FutureBuilder( ///异步加载数据 future: loadingLocalAsset(), ///构建 builder: (BuildContext context, var snap) { ///加载完成的html数据 String htmlData = snap.data; //使用插件 FaiWebViewWidget if (htmlData == null) { return CircularProgressIndicator(); } ///通过配置 htmlBlockData 来渲染 return FaiWebViewWidget( //webview 加载本地html数据 htmlBlockData: htmlData, //webview 加载信息回调 callback: callBack, //输出日志 isLog: true, ); }, ); } ``` [对应的完整代码在这里](https://github.com/zhaolongs/Flutter_Fai_Webview/blob/master/example/lib/exampl_default_html_data_max_refresh2.dart) ##### 2.3 加载混合页面 也就是说 一个页面中,一部分是 Flutter Widget 一部分是 webview 加载。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190720154728780.gif) ```java import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_fai_webview/flutter_fai_webview.dart'; /** * 混合页面加载 * */ class DefaultHexRefreshPage extends StatefulWidget { @override MaxUrlHexRefreshState createState() => MaxUrlHexRefreshState(); } class MaxUrlHexRefreshState extends State { //原生 发送给 Flutter 的消息 String message = "--"; String htmlUrl = "https://blog.csdn.net/zl18603543572"; double webViewHeight = 1; @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( onPressed: () { Navigator.pop(context); }, icon: Icon(Icons.arrow_back_ios), ), title: Container( padding: EdgeInsets.only(left: 10, right: 10), height: 28, alignment: Alignment(0, 0), color: Color.fromARGB(90, 0, 0, 0), child: Text( message, style: TextStyle(color: Colors.white, fontSize: 12), ), ), ), body: buildRefreshHexWidget(), ); } /** * 需要注意的是 * RefreshIndicator 会覆盖 WebView 的滑动事件 * 所有关于 监听 WebView 的滑动监听将会失效 */ Widget buildRefreshHexWidget() { return RefreshIndicator( //下拉刷新触发方法 onRefresh: _onRefresh, //设置webViewWidget child: SingleChildScrollView( child: Column( children: [ Container( color: Colors.grey, height: 220.0, child: Column(mainAxisSize: MainAxisSize.min,children: [ Center(child: Text("这里是 Flutter widget "),) ],), ), Align( alignment: Alignment(0, 0), child: Text("以下是 Html 页面 "), ), Container( color: Colors.redAccent, height: 1.0, ), Container( height: webViewHeight, child: FaiWebViewWidget( //webview 加载网页链接 url: htmlUrl, //webview 加载信息回调 callback: callBack, //输出日志 isLog: true, ), ) ], ), ), ); } Future _onRefresh() async { return await Future.delayed(Duration(seconds: 1), () { print('refresh'); webViewWidget.refresh(); }); } callBack(int ?code, String ?msg, content) { //加载页面完成后 对页面重新测量的回调 if (code == 201) { //更新高度 webViewHeight = content; print("webViewHeight " + content.toString()); } else { //其他回调 } setState(() { message = "回调:code[" + code.toString() + "]; msg[" + msg.toString() + "]"; }); } } ``` #### 3 Flutter与Html中JS的双向互调 在这里使用到的Html页面代码如下: ```html


测试一下  




``` #### 3.1 Flutter中调用JS中的方法 Flutter中调用JS方法,需要使用到FaiWebViewController,代码如下: ```java ///webView控制器 FaiWebViewController faiWebViewController = new FaiWebViewController(); ///构建webview组件 FaiWebViewWidget buildFaiWebViewWidget(String htmlData) { return FaiWebViewWidget( controller: faiWebViewController, //webview 加载本地html数据 htmlBlockData: htmlData, //webview 加载信息回调 callback: callBack, //输出日志 isLog: true, ); } ``` 然后在点击按钮的时候调用HTML中JS的方法,代码如下: ```java RaisedButton( child: Text("Flutter调用JS方法"), onPressed: () { ///向JS方法中传的参数 Map map = new Map(); map["test"] = "这是Flutter中传的参数"; ///参数一为调用JS的方法名称 ///参数二为向JS中传递的参数 faiWebViewController.toJsFunction( jsMethodName: "testAlert2", parameterMap: map); }, ) ``` 这里使用到的testAlert2就是JS中声明的方法 #### 3.2 JS中调用Flutter的方法 在上述描述到的Html文件中,声明了一个按钮,然后在点击按钮时调用 toFlutter方法,然后在Flutter中对应的FaiWebViewWidget设置的callback监听中会收到这个回调,处理代码如下: ```java ///FaiWebViewWidget 的回调处理 callBack(int ?code, String ?msg, content) { if (code == 202) { /// json.encode(mapData); //Map转化JSON字符串 /// json.decode(strData); //JSON 字符串转化为Map类型 Map map = json.decode(content); String name = map["name"]; int age = map["age"]; print('这里是Js调用到Flutter中的数据 name $name age $age'); } else { //其他回调 } setState(() { message = "回调:code[" + code.toString() + "]; msg[" + msg.toString() + "]"; }); } ``` 然后在这里接收到JS中的调用以及参数后,就可以在Flutter中做任何想要的操作 > 在正个过程中参数数据是通过json格式的数据来传递的,在实际项目开发中需要保持json的一至 *** #### 4 Flutter操作Html的其他方法简述 ##### 4.1 刷新页面加载 通过 FaiWebViewController的 refresh方法就可实现,代码如下: ```java faiWebViewController.refresh(); ``` ##### 4.2 浏览器的后退 ```java ///判断是否可退 如果可退 bool back = await _faiWebViewController.canBack(); print("是否可后退 $back"); if (back) { /// 如果可退 后退浏览器的历史 _faiWebViewController.back(); } else { ///如果不可就退出当前页面 Navigator.of(context).pop(); } ``` ##### 4.3 浏览器的前进 ```java ///判断是否可前进 bool forword = await _faiWebViewController.canForword(); print("是否可前进$forword"); if (forword) { /// 如果可退 后退浏览器的历史 _faiWebViewController.forword(); } else {} ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200726173630806.gif) *** 完结 ![公众号 我的大前端生涯](https://img-blog.csdnimg.cn/20200620175409480.gif)