flutter_fai_webview

更多文章请查看 flutter从入门 到精通

可以用来加载 Html 页面,以实现 Android 中 WebView 或者 是 iOS 中的 UIWebView 中的功能。

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 中。

插件源码在这里

开发插件要具备的知识:

Flutter_Fai_Webview 插件可实现的功能:

  • 同时适配于 Android Ios 两个平台
  • 通过 url 来加载渲染一个Html 页面
  • 加载 Html 文本数据 如 <html> .... </html>
  • 加载 Html 标签数据 如 <p> ... </p>
  • 实现 WebView 加载完成后,自动测量 WebView 的高度,并回调 Flutter
  • 实现 WebView 加载完成监听
  • 实现 WebView 上下滑动、滑动到顶部兼听、滑动到底部兼听并回调 Flutter
  • 实现 兼听 WebView 输出日志并将日志回调 Flutter
  • 实现 为 Html 页面中所有的图片添加点击事件 并回调 Flutter
  • 实现 Html 中Js 调用 Flutter 页面功能
  • 实现 Flutter 页面中 触发 Html 页面中 Js 方法

本插件开发的过程将在这里详细论述

也就是说在这里将教会你 开发一个 Flutter 插件。 Flutter 加载 HTML 详细阐述(iOS 端实现) Flutter 加载 HTML 详细阐述(Android 端实现)


开始使用

1 基本使用说明

1.1 Flutter 项目中 pubspec.xml 文件中 配置插件
  flutter_fai_webview:
    git:
      url: https://github.com/zhaolongs/Flutter_Fai_Webview.git
      ref: master
1.2 在使用到 WebView 页面中

引入头文件

import 'package:flutter_fai_webview/flutter_fai_webview.dart';
1.3 创建 WebView 组件

    //使用插件 FaiWebViewWidget
    webViewWidget = FaiWebViewWidget(
      //webview 加载网页链接
      url: htmlUrl,
      //webview 加载信息回调
      callback: callBack,
      //输出日志
      isLog: true,
    );
    
1.4 FaiWebViewWidget 构造参数说明
  FaiWebViewWidget({
    //webview 加载网页链接
    this.url,
    //webview 加载 完整的 html 文件数据  如 <html> .... </html>
    // 不完整的 html 文件数据 如 <p></p> 配置到此项,用此属性来加载,只会渲染 <p> ... </p> 中已有的样式 不会适配移动端显示
    this.htmlData,
    //webview 加载完整的 html 文件数据 或者是 不完整的 html 文件数据 如 <p></p>
    //不完整的 html 文件数据 如 <p></p> 配置到此项,会自动将不完整的 html 文件数据 添加 <html><head> .. </head> <body> 原来的内容 </body></html>,并适配移动端
    this.htmlBlockData,
    //输出 Log 日志功能
    this.isLog,
    // 为 Html 页面中所有的图片添加 点击事件 并通过回调 通知 Flutter 页面
    // 只有使用 htmlBlockData 属性加载的页面才会有此效果
    this.htmlImageIsClick = false,
    // Html 页面中图片点击回调
    this.imageCallBack,
    // Html 页面中所有的消息回调
    this.callback,
  });

1.5 原生回调 Flutter 的 callback 以及 Html 页面中 图片 点击回调说明
  /**
   * code 原生 Android iOS 回调 Flutter 的消息类型标识
   * message 消息类型日志
   * content 回调的基本数据
   */
  Function(int code, String message, dynamic content) callback;

详细说明


  //当前点击的图片 URL
  String imageUrl = null;
  //是否显示浮动按钮
  bool isShowFloat = false;
  /**
   * code 当前点击图片的 位置
   * url 当前点击图片对应的 链接
   * images 当前 Html 页面中所有的图片集合
   */
  void imageCallBack(int code, String url, List<String> 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;
    });
  }
1.6 Flutter 刷新页面
//调用此方法 便可刷新(重新加载页面)
webViewWidget.refresh();
1.7 Flutter 调用 JS 方法
//testAlert() 就是我们要调用的 Html 页面中 JS的方法
// testAlert() 可以自定义与 Html 中的 JS 开发约定
webViewWidget.loadJsMethod("testAlert()");

2 Flutter 加载页面

2.1 通过 url 加载 Html 页面

在这里插入图片描述

import 'package:flutter/material.dart';
import 'package:flutter_fai_webview/flutter_fai_webview.dart';

/**
 *  加载地址 
 *  通过 url 加载了一个 Html页面 是取常用的方法
 */
class DefaultLoadingWebViewUrlPage extends StatefulWidget {
  @override
  MaxUrlState createState() => MaxUrlState();
}

class MaxUrlState extends State<DefaultLoadingWebViewUrlPage> {

  //要显示的页面内容
  Widget childWidget;
  //加载Html的View
  FaiWebViewWidget webViewWidget;
  //原生 发送给 Flutter 的消息
  String message = "--";
  // 页面
  String htmlUrl = "https://blog.csdn.net/zl18603543572";

  @override
  void initState() {
    super.initState();
    //使用插件 FaiWebViewWidget
    webViewWidget = FaiWebViewWidget(
      //webview 加载网页链接
      url: htmlUrl,
      //webview 加载信息回调
      callback: callBack,
      //输出日志
      isLog: true,
    );
  }

  @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: webViewWidget,
      );

  }

  void callBack(int code, String msg, content) {
    //加载页面完成后 对页面重新测量的回调
    //这里没有使用到
    //当FaiWebViewWidget 被嵌套在可滑动的 widget 中,必须设置 FaiWebViewWidget 的高度
    //设置 FaiWebViewWidget 的高度 可通过在 FaiWebViewWidget 嵌套一层 Container 或者 SizeBox
    if (code == 201) {
      //页面加载完成后 测量的 WebView 高度
      int webViewHeight = content;
      print("webViewHeight " + webViewHeight.toString());
    } else {
      //其他回调
    }
    setState(() {
      message = "回调:code[" + code.toString() + "]; msg[" + msg.toString() + "]";
    });
  }
}

2.2 加载本地资源目录assets下的html

** 通过 Html Data 加载 Html 页面 **

2.2.1 加载模拟的html数据

在这里插入图片描述

import 'package:flutter/material.dart';
import 'package:flutter_fai_webview/flutter_fai_webview.dart';

/**
 *  通过 htmlBlockData 加载 Html 数据 并添加移动适配
 */
class DefaultHtmlBlockDataPage2 extends StatefulWidget {
  @override
  DefaultHtmlBlockDataPageState createState() =>
      DefaultHtmlBlockDataPageState();
}

class DefaultHtmlBlockDataPageState extends State<DefaultHtmlBlockDataPage2> {


  FaiWebViewWidget webViewWidget;
  //原生 发送给 Flutter 的消息
  String message = "--";
  double webViewHeight = 100;

  //要显示的页面内容
  Widget childWidget;
  String htmlBlockData = "<!DOCTYPE html><html> <head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1\"> </head> <body><p><br/></p><p>生物真题&nbsp;</p><p><img src=\"https://images.gitee.com/uploads/images/2020/0602/203000_9fa3ddaa_568055.png\" title=\"\" alt=\"\"/></p><p><img src=\"https://images.gitee.com/uploads/images/2020/0602/203000_9fa3ddaa_568055.png\" title=\"\" alt=\"\"/></p><p><br/></p><p><br/></p><p><br/></p></body></html>";

  @override
  void initState() {
    super.initState();

    //使用插件 FaiWebViewWidget
    webViewWidget = FaiWebViewWidget(
      //webview 加载网页链接
      htmlBlockData: htmlBlockData,
      //webview 加载信息回调
      callback: callBack,
      //输出日志
      isLog: true,
    );
  }

  @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: Container(child: webViewWidget,),
    );
  }

  callBack(int code, String msg, content) {
    //加载页面完成后 对页面重新测量的回调
    //这里没有使用到
    //当FaiWebViewWidget 被嵌套在可滑动的 widget 中,必须设置 FaiWebViewWidget 的高度
    //设置 FaiWebViewWidget 的高度 可通过在 FaiWebViewWidget 嵌套一层 Container 或者 SizeBox
    if (code == 201) {
      webViewHeight = content;
      print("webViewHeight " + webViewHeight.toString());
    } else {
      //其他回调
    }
    setState(() {
      message = "回调:code[" + code.toString() + "]; msg[" + msg.toString() + "]";
    });
  }
}


2.2.2 加载资源目录下assets的html数据

第一步是配制资源目录下assets的html的依赖,在项目的pubspec.yaml中添加依赖

assets:
  - assets/html/

第二步是加载对应的html静态文件然后渲染出来

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_fai_webview/flutter_fai_webview.dart';

/**
 *  通过 htmlBlockData 加载 Html 数据 并添加移动适配
 */
class DefaultHtmlBlockDataPage2 extends StatefulWidget {
  @override
  DefaultHtmlBlockDataPageState createState() =>
      DefaultHtmlBlockDataPageState();
}

class DefaultHtmlBlockDataPageState extends State<DefaultHtmlBlockDataPage2> {
  //原生 发送给 Flutter 的消息
  String message = "--";
  double webViewHeight = 100;

  //要显示的页面内容
  Widget childWidget;
  String htmlBlockData =
      "<!DOCTYPE html><html> <head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1\"> </head> <body><p>加载中</p></body></html>";

  @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: Container(
        ///使用异步来加载
        child: FutureBuilder<String>(
          ///异步加载数据
          future: loadingLocalAsset(),
          ///构建
          builder: (BuildContext context, var snap) {
            String htmlData = snap.data;
            //使用插件 FaiWebViewWidget
            if (htmlData == null) {
              return CircularProgressIndicator();
            }
            return FaiWebViewWidget(
              //webview 加载本地html数据
              htmlBlockData: htmlData,
              //webview 加载信息回调
              callback: callBack,
              //输出日志
              isLog: true,
            );
          },
        ),
      ),
    );
  }

  callBack(int code, String msg, content) {
    //加载页面完成后 对页面重新测量的回调
    //这里没有使用到
    //当FaiWebViewWidget 被嵌套在可滑动的 widget 中,必须设置 FaiWebViewWidget 的高度
    //设置 FaiWebViewWidget 的高度 可通过在 FaiWebViewWidget 嵌套一层 Container 或者 SizeBox
    if (code == 201) {
      webViewHeight = content;
      print("webViewHeight " + webViewHeight.toString());
    } else {
      //其他回调
    }
    setState(() {
      message = "回调:code[" + code.toString() + "]; msg[" + msg.toString() + "]";
    });
  }

  Future<String> loadingLocalAsset() async {
    String htmlData = await rootBundle.loadString('assets/html/test.html');
    print("加载数据完成 $htmlData");
    return htmlData;
  }
}


2.3 加载混合页面

也就是说 一个页面中,一部分是 Flutter Widget 一部分是 webview 加载。

在这里插入图片描述

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<DefaultHexRefreshPage> {
  FaiWebViewWidget webViewWidget;

  //原生 发送给 Flutter 的消息
  String message = "--";
  String htmlUrl = "https://blog.csdn.net/zl18603543572";
  double webViewHeight = 1;

  @override
  void initState() {
    super.initState();

    //使用插件 FaiWebViewWidget
    webViewWidget = FaiWebViewWidget(
      //webview 加载网页链接
      url: htmlUrl,
      //webview 加载信息回调
      callback: callBack,
      //输出日志
      isLog: true,
    );
  }

  @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: <Widget>[
            Container(
              color: Colors.grey,
              height: 220.0,
              child: Column(mainAxisSize: MainAxisSize.min,children: <Widget>[
                  Center(child: Text("这里是 Flutter widget  "),)
              ],),
            ),
            Align(
              alignment: Alignment(0, 0),
              child: Text("以下是 Html 页面 "),
            ),
            Container(
              color: Colors.redAccent,
              height: 1.0,
            ),
            Container(
              height: webViewHeight,
              child: webViewWidget,
            )
          ],
        ),
      ),
    );
  }

  Future<Null> _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() + "]";
    });
  }
}

Libraries

flutter_fai_webview