# 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;
});
}
```
***

***
#### 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/
```
对应的文件目录如下所示:

然后就是使用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 加载。

```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 {}
```

***
完结
