版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!
情感语录: 心若计较,时时都有怨言;心若宽容,处处都是晴天。
由于个人原因导致好久没更新文章了,在第十九章前的内容都是19年前所编写,最新的内容会在后面持续更新。 本篇内容带来的是 WebView 在Flutter 中的使用。在 Flutter 中第三方的WebView是非常多的,如:
tweet_webview, interactive_webview , flutter_webview_plugin 等。但这个些插件本章不做介绍,喜欢的童鞋可自行研究,本章主要介绍的是官方的 webview 那么WebView 究竟可以拿来做什么呢? WebView 可以解析DOM 元素,展示html页面的控件,它和浏览器展示页面的原理是相同的,所以可以把它当做浏览器看待。
一、WebView 的引入
1、在pubspec.yaml
文件中添加依赖
dependencies:
fluttertoast: ^3.0.3
flutter:
sdk: flutter
#添加 WebView 插件
webview_flutter: ^0.3.15+1
本人使用的当前最新版本 0.3.15,读者想体验最新版本请在使用时参看最新版本号进行替换,特别需要注意的是 添加插件时一定的注意代码的缩进问题,多一个空格或者少一个都将导致引入不成功。
2、安装依赖库
执行 flutter packages get
命令;AS 开发工具直接右上角 packages get
也可。
3、在需要使用的地方导包引入
import 'package:webview_flutter/webview_flutter.dart';
4、IOS 配置文件配置
在IOS目录下 Runner 包下的 Info.plist
文件中添加如下配置:
<key>io.flutter.embedded_views_preview</key>
<string>YES</string>
请严格按照官方文档进行配置,否则在设备上可能出现运行错误。
二、WebView 构造函数
const WebView({
Key key,
this.onWebViewCreated,
this.initialUrl,
this.javascriptMode = JavascriptMode.disabled,
this.javascriptChannels,
this.navigationDelegate,
this.gestureRecognizers,
this.onPageFinished,
this.debuggingEnabled = false,
this.userAgent,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
})
常用属性:
属性 描述
onWebViewCreated WebView创建完毕时的回调
initialUrl WebView加载的地址
javascriptMode 是否开启支持 JS 模式(默认不开启)
javascriptChannels 配置 JS 调用flutter
navigationDelegate 路由委托(可拦截需要特殊处理的地址等)
gestureRecognizers 手势处理(一般不用)
onPageFinished 页面加载完成时的回调
initialMediaPlaybackPolicy 页面中的媒体播放权限配置
上面说到 WebView 可以看做是一个浏览器,下面 就使用 它来装载下网页试试呢? 试试就试试 直接撸 O(∩_∩)O
实例代码:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return WebViewPageState();
}
}
class WebViewPageState extends State<WebViewPage> {
String _title = "webview";
@override
Widget build(BuildContext context) {
// 使用 IOS 风格
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
//添加一个标题
middle: Text("$_title"),
),
child: SafeArea(
child: WebView(
initialUrl: "http://www.baidu.com",
)
),
);
}
}
这里使用到了 SafeArea
组件,这个组件是前面章节中没介绍过的,它的作用主要是解决一些异型屏幕的适配问题,比如在常规屏幕上 你的内容文字能正常显示,但放在刘海屏上可能就被遮挡住了,因此使用 SafeArea 可解决此类问题。 它的使用也是非常简单,这里不做过多介绍。
实例效果
在创建 WebView 后 只是简单的 配置 initialUrl
属性就将百度的所有内容装载了进来,在开发中很多页面是需要使用网页的方式进行显示,因此选用 WebView 可极大较轻开发成本,这也是它的魅力之处,简单除暴。
下面来看下稍微复杂点的案例,这里会使用到上面的几个回调函数。
实例代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_learn/util/ToastUtil.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return WebViewPageState();
}
}
class WebViewPageState extends State<WebViewPage> {
WebViewController _controller;
String _title = "webview";
@override
Widget build(BuildContext context) {
// 使用 IOS 风格
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
//添加一个标题
middle: Text("$_title"),
),
child: SafeArea(
child: WebView(
initialUrl: "http://www.baidu.com",
//开启Js 支持
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
//拿到controller
_controller = controller;
},
onPageFinished: (url) {
//获取网页的标题来显示
_controller.evaluateJavascript("document.title").then((result) {
setState(() {
_title = result;
});
}
);
print("输出当前地址:" + url);
},
navigationDelegate: (NavigationRequest request) {
//拦截 百度账号登录跳转
if (request.url.startsWith(
"https://wappass.baidu.com/passport/?login")) {
ToastUtil.show("请求被拦截到诺 哈哈");
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
)
),
);
}
}
这段代码也是非常简单,首先在 WebView创建完毕后 通过 onWebViewCreated
回调函数拿到 WebViewController ,接下来在 页面加载完成时(onPageFinished被触发) 通过 WebViewController 的 evaluateJavascript()
方法可以拿到 JS 的信息。最后在 路由委托中(navigationDelegate) 处理了下百度账号登录页面跳转的拦截。
实例效果
温馨提示:
NavigationDecision.prevent
:阻止路由,返回此枚举类型 即阻止当前页面跳转。
NavigationDecision.navigate
:允许路由,返回此枚举类型 即允许所有的请求跳转。
上面的所有演示都只是对页面的一些装载显示过程而已,在实际开发中可能并不是这么简单,往往会涉及到和 JavaScript 通信问题,相互传值等。接下来所介绍的内容才是本章节中最为重要的知识点。端正姿势小板凳上坐好 O(∩_∩)O
三、WebView 和 JavaScript 相互通信
WebView 和 JS 的通信常常分两种情况:第一种是 JS或者html 文件放在本地即 App内部。第二种是需要通信的 JS 放在服务器端的。本章主要介绍的是第一种,其目的是因为放在本地都会了,放在服务器端的就肯定会了。同时使用本地的方式 会引出一个知识点 AssetBundle
,这也是前面章节所没介绍过的。
1、工程中新建 assets
目录
在工程中 新建 assets 目录,然后创建用于测试的 html 文件。结构大致如下:
2、JS 向 Flutter 传值
2.1 编写 html 代码,添加一个按钮,点击按钮后向 Flutter 发送数据。
<html lang="zh-CN">
<head>
<title>测试网页</title>
</head>
<body>
<p>这是一段很长很长的故事..... </p>
</body>
<!-- 点击按钮将 信息传给 flutter -->
<button onclick="flutterTest.postMessage('来自js的传值!')">发送数据给客户端</button>
</html>
2.2 编写 flutter 代码,接收 js传过来的值
javascriptChannels: <JavascriptChannel>[
JavascriptChannel(
// 双方约定好的 协议
name: "flutterTest",
onMessageReceived: (JavascriptMessage message) {
print("输出参数: ${message.message}");
ToastUtil.show(message.message);
}
),
].toSet(),
下面来看下运行效果:
温馨提示:
① 其中 flutterTest
为 Flutter 和 JS 双方协商好的协议值,你可以随意更改,但任何一方修改,另一方也都必须修改。
② JS 向 Flutter 发送数据通过 协议值+postMessage("value")
方式进行。
3、Flutter 向 JS 传值
3.1 编写 html 代码,添加一个方法提供给 Flutter 调用
<html lang="zh-CN">
<p id='testContent'>hello world</p>
<head>
<title>测试网页</title>
</head>
<body>
<p>这是一段很长很长的故事..... </p>
</body>
<!-- 提供给flutter 调用的方法 -->
<script>
function flutterCallJs(message){
document.getElementById("testContent").innerHTML = message
}
</script>
<!-- 点击按钮将 信息传给 flutter -->
<button onclick="flutterTest.postMessage('来自js的传值!')">发送数据给客户端</button>
</html>
3.2、Flutter 端代调用 JS 提供的方法
///FloatingActionButton
floatingActionButton: FloatingActionButton(
elevation: 0,
child: Icon(
Icons.send,
size: 30,
),
onPressed: () {
//点击触发 调用 js 方法
_controller.evaluateJavascript("flutterCallJs('这是客户端传过来的值')");
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
运行效果:
在Flutter 中 evaluateJavascript 方法成功触发了 JS 的方法并将值携带了过去,可以看到 id 为 testContent 元素标签的内容也被修改了,说明传值是OK的。
温馨提示:
① 其中 flutterCallJs
为 JS 接收 Flutter 传值的方法,该方法由 JS 端先定义好提供给Flutter。
② Flutter 通过 evaluateJavascript 方法
可以直接调用 JS 提供的方法。
3.3、Flutter 端完整代码如下:
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_learn/util/ToastUtil.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return WebViewPageState();
}
}
class WebViewPageState extends State<WebViewPage> {
WebViewController _controller;
String _title = "webview";
@override
Widget build(BuildContext context) {
return Scaffold(
///FloatingActionButton
floatingActionButton: FloatingActionButton(
elevation: 0,
child: Icon(
Icons.send,
size: 30,
),
onPressed: () {
//点击触发 调用 js 方法
_controller.evaluateJavascript("flutterCallJs('这是客户端传过来的值')");
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
appBar: AppBar(
title: Text(_title),
// 控制 WebView 返回(依次返回界面)
leading: IconButton(
icon: Icon(Icons.arrow_back,color: Colors.white,),
onPressed:(){
_controller.canGoBack().then((value){
if(value){
_controller.goBack();
}else{
Navigator.pop(context);
}
});
},
)
),
body: SafeArea(
child: WebView(
initialUrl: "",
//开启Js 支持
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
//拿到controller
_controller = controller;
_loadHtmlFromAssets();
},
onPageFinished: (url) {
//获取网页的标题来显示
_controller.evaluateJavascript("document.title").then((result) {
setState(() {
_title = result;
});
});
print("输出当前地址:" + url);
},
javascriptChannels: <JavascriptChannel>[
JavascriptChannel(
// 双方约定好的 协议
name: "flutterTest",
onMessageReceived: (JavascriptMessage message) {
print("输出参数: ${message.message}");
ToastUtil.show(message.message);
}
),
].toSet(),
)
),
);
}
// 从本地加载html文件,需要使用异步操作
_loadHtmlFromAssets() async {
String fileText = await rootBundle.loadString('assets/html/JavaScriptTest.html');
_controller.loadUrl(Uri.dataFromString(fileText, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString());
}
}
Flutter端需要注意以下要点:
① initialUrl
属性可以不写,或者赋值空字符串即可。
② javascriptMode
属性必须指定为不受限模式(JavascriptMode.unrestricted) 否则不能通信。
③ javascriptChannels
配置 JS 通道,用于 JS向 flutter发送数据。其中 name
属性是双方协商好的协议值。 传递过来的值被 onMessageReceived 函数的 JavascriptMessage
接收。
④ Flutter 中读取 assets 文件是通过 AssetBundle
对象加载的,WebViewController 在加载本地网页时和原生其实一样的,这里只是多了一个 Uri
来进行包装。
⑤ 如果你的JS 文件在 服务端 直接配置 initialUrl
属性即可,无需要 ④ 中的操作。
⑥ 如果是复杂的数据类型请封装成json
字符串,确保在 Android 和 IOS 都能支持。
写在最后
笔者在写这篇文章时是当时最新版本(0.3.15)相比之前版本解决了很多问题,但还是存在很多不足之处。如果你的需求不是特别复杂使用官方的即可,跟着官方持续跟进。
好了本章节到此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星;你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O
实例源码地址:https://github.com/zhengzaihong/flutter_learn/blob/master/lib/page/webview/WebViewPage.dart