Flutter 中 WebView 的使用以及与 JS 交互

WebView 的使用,算得上是比较普遍的,特别是与 JS 的交互,今天整理一下在 flutter 中使用 WebView 的一些事~
重点讲解如下两个主流插件的使用:

官方插件:webview_flutter
pub 比较好用的插件:flutter_webview_plugin

任何一个插件的使用,都是两步走:
1.引入依赖
2.导入使用,应用组件(widget)

但是这个插件的使用过程中,在 IOS 里边需要单独设置一项,不然会报错。如下,在ios/Runner/Info.plist中添加

<key>io.flutter.embedded_views_preview</key>
<string>YES</string>*

本篇重点先练习官方插件的使用。
为了便于区分,将两个插件的使用放到了一个 dart 文件里边,如下代码:

class WidgetWebview extends StatefulWidget {
  String remoteUrl = "https://www.baidu.com";
  String localUrl = "assets/html/login.html";
  bool useWebviewFlutter = true; // 是否使用 flutter 提供的插件

  @override
  _WidgetWebviewState createState() =>
      useWebviewFlutter ? _WebviewFlutterState() : _FlutterWebViewPluginState();
}
/// 方便将两个插件放在一起对比~
abstract class _WidgetWebviewState extends State<WidgetWebview> {}

class _WebviewFlutterState extends _WidgetWebviewState {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("WebViewFlutter 与 JS 交互"),
      ),
    );
  }
}

class _FlutterWebViewPluginState extends _WidgetWebviewState {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("FlutterWebViewPlugin 与 JS 交互"),
      ),
    );
  }
}

然后本文会对 _WebviewFlutterState 进行处理

基础使用

ios中在ios/Runner/Info.plist中添加 不然页面加载不出来滴...

<key>io.flutter.embedded_views_preview</key>
<string>YES</string>*

首先用 WebView 来加载一个页面,很简单,只需要在 build() 方法中加入 WebView 组件即可,
如下是他的构造函数

  const WebView({
    Key key,
    this.onWebViewCreated, //WebView 创建完成之后的回调
    this.initialUrl, // 初始化 URL
    this.javascriptMode = JavascriptMode.disabled, // JS执行模式 默认是不调用
    this.javascriptChannels, // JS可以调用Flutter 的通道
    this.navigationDelegate, // 路由委托(可以通过在此处拦截url实现JS调用Flutter部分) 拦截 URL
    this.gestureRecognizers, // 手势相关
    this.onPageFinished, // 页面加载完成的回调
    this.debuggingEnabled = false,
    this.userAgent,
    this.initialMediaPlaybackPolicy =
        AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
  })

其实通过构造函数的简单了解,基本上学会了他的使用了,那么如何最简单的加载一个 URL 呢?

      body: WebView(
        initialUrl: widget.remoteUrl,
      ),

是不是很简单呢这就是最简单的使用,用来加载一个页面啦

进阶使用

上边说了怎么简单的加载一个页面,那么继续会有一些其他的用法可能会用到呢~
WebView 有一个控制器,叫做 WebViewController,用来处理一些事情,可以在 WebView.onWebViewCreated 中获取到他。

获取页面 title

很多时候,需要将当前 flutter 页面的appbar 标题,显示为网页里边的 title,那么该怎么获取这个 title 呢?前边说的 WebViewController 内有一个 getTitle() 方法,可以用来获取网页的 title,如下:

  /// 获取当前加载页面的 title
  _getTitle() async {
    String title = await _webViewController.getTitle();
    print("title---$title");
  }

从这获取到了 title 字符串,然后你就可以用来做处理了。

加载本地 HTML 文件

直接上代码,需要注意的地方是:
1.Bundle.loadString() 是异步执行,所以说需要用 FutureBuilder 对组件进行处理一下子。
2.要用 Uri 对文件进行转码,如代码所示
3.本地文件加入依赖时,要注意路径的准确性!此处我是在我的 assets 目录里边,创建了一个 html 的目录,将 HTML 文件放到这里边了,所以 依赖里边就得用具体到 html 路径。

  assets:
    - assets/
    - assets/html/
 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: FutureBuilder(
        future: _loadHtmlFile(),
        builder: (context, snapshot) {
          return WebView(
            initialUrl: Uri.dataFromString(snapshot.data, mimeType: 'text/html', encoding: Encoding.getByName('utf-8'))
                      .toString(),
            onWebViewCreated: (WebViewController controller) {
              print("webview page: webview created...");
              _webViewController = controller;
              _webViewController.loadUrl(
                  new Uri.dataFromString(snapshot.data, mimeType: 'text/html', encoding: Encoding.getByName('utf-8'))
                      .toString());
            },
            onPageFinished: (url) {
              print("webview page: load finished...url=$url");
              _getTitle();
            },
          );
        },
      ),
    );
  }

JS 调用 flutter 方法

重头戏来了,在 HTML 里边来调用 flutter 中的方法,前边讲过,JS调用Flutter有两种方法:使用javascriptChannels 发送消息和使用路由委托navigationDelegate 拦截url 实现交互。
拦截的实现暂时不讲,个人觉得比较鸡肋,感兴趣的小伙伴可以自己去研究研究哈(偷笑...)

使用 javascriptChannels 发送消息

javascriptChannels 参数是一个数组形式的,意味着可以给每一个要交互的方法创建一个对应的对象(JavascriptChannel),扔到这数组里边。但是我觉得也可以考虑就创建一个对象,后续通过 JavascriptChannel 里边的键来区分处理,但是暂时不行,因为 JavascriptMessage 里边只有一个参数,还是个 string 类型,所以不太好容易处理。。。本文使用第一种方法
比如现在我要在 js 里边调用一个 flutter 的 toast 方法来显示一个 toast,那么第一步就是创建一个 JavascriptChannel如下:

  // 创建 JavascriptChannel
  JavascriptChannel _toastJsChannel(BuildContext context) => JavascriptChannel(
      name: 'show_flutter_toast',
      onMessageReceived: (JavascriptMessage message) {
        print("get message from JS, message is: ${message.message}");
        T.show(message.message);
      });
javascriptChannels: [_toastJsChannel(context)].toSet(),

要说明的地方是:
1.name 参数,对应的是一个 String 类型的键,这个就是在 JS 里边要调用的(postMessage("xxx")),必须一致。
2.onMessageReceived 回调了一个 JavascriptMessage 对象,接收来自 JS 的回调信息。目前这里边只有一个 message(String) 属性。只支持 String 类型的参数,数据过多的话可以考虑 JSON 的 String 类型参数~
接下来就是去书写简单的 JS 代码啦~~

<div class = "rect" style="margin-top: 50px;" onclick="flutterShowToast()">JS调用Flutter</div>

  /// 调用 flutter 的方法
  /// -name:show_flutter_toast
  function flutterShowToast() {
    show_flutter_toast.postMessage("message from JS...");
  }

HTML 里边的代码很简单,就是给一个 div 设置了点击事件,调了 flutterShowToast 这个方法,特别需要注意的一点是show_flutter_toast 一定不要写错了!(此处应该知道为什么必须用这个字符串哈~)

flutter 调用 JS

在WebView创建完成之后,我们可以拿到一个WebViewController,通过它的evaluateJavascript()方法,我们可以执行JS语句

WebViewController _webViewController;
...
onWebViewCreated: (WebViewController controller) {
              print("webview page: webview created...");
              _webViewController = controller;
              _webViewController.loadUrl(new Uri.dataFromString(snapshot.data,
                      mimeType: 'text/html',
                      encoding: Encoding.getByName('utf-8'))
                  .toString());
            }
...
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: (){
          _webViewController.evaluateJavascript("flutterCallJsMethod('message from Flutter!')");
        },
      ),

通过在页面里边添加了一个 FloatingActionButton 来触发传递事件,如上所说,调用了evaluateJavascript 方法,内容就是 JS 里边的方法以及参数,然后在 HTML 里边对应的写一个同名的 function,就可以实现 从 flutter 到 jS 的传递了,HTML 代码如下:

  /// Flutter 调用 JS 的此方法
  /// 在 Flutter 中通过 _webViewController.evaluateJavascript("flutterCallJsMethod('message from Flutter!')") 调用
  function flutterCallJsMethod(message) {
      document.getElementById("show_info").innerText = message;
  }

同时 evaluateJavascript 返回了一个 Future 对象,可以用来接手 JS 回传的内容。
注意一点:evaluateJavascript()方法,Flutter建议我们在onPageFinished回调之后去执行,以保证所有的HTML都已经加载完毕了。因此在实际开发中,建议参考我的写法,将 WebView 放到 FutureBuilder 里边~

完事暂时就先整理这么多了希望对大家有用吧~

源码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容