flutter页面多个webviewb实现方案

场景

当我们计划使用flutter开发app时,遇到一个页面有多个由富文本编辑器编辑的html内容场景,而且内容很长很大,但是都是一些图文展示的内容。

  1. 使用和小程序中一样的方案对html字符串进行转换,所以使用了flutter_html这个库,但是结果是卡异常的卡顿,而且样式无法统一。

  2. 结合服务端将html字符串生成一个个单独的静态网页,如https://www.testhtml.com/qweasdgtrtyytu.html,文件名随机。利用官方库webview_flutter进行渲染,因为是需要完整展示页面内容所以需要动态获取网页高度并设置flutter组件的高度(下面具体讲解),实现之后当页面webview太长时安卓闪退,由于不太懂安卓webview的机制,所以无法解决。

  3. 使用三方webview库flutter_inappwebview完美解决了以上2个方法无法解决的问题,样式统一,可设置高度,以下我们具体聊聊如何实现。

  • 引入库,版本6.0
dart 复制代码
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  • 定义一个Model用来存放webview的相关信息
dart 复制代码
class WebViewModel {
  String title;
  double height;
  double width;
  HeadlessInAppWebView? headlessWebView;
  int progress;
  String url;
  bool convertFlag;

  WebViewModel({
    required this.title,
    this.width = 0,
    this.height = 0,
    this.headlessWebView,
    this.progress = 0,
    this.url = '',
    this.convertFlag = false,
  });
}
  • 初始化相关属性
dart 复制代码
final List<WebViewModel> webviewList = [
    WebViewModel(title: '页面一', height: 0, width: 0, url: '', progress: 0, headlessWebView: null),
    WebViewModel(title: '页面二', height: 0, width: 0, url: '', progress: 0, headlessWebView: null),
    WebViewModel(title: '页面三', height: 0, width: 0, url: '', progress: 0, headlessWebView: null),
  ];

// 加载多个webview
for (var item in webviewList) {
  HeadlessInAppWebView? headlessWebView = item.headlessWebView;
  if (headlessWebView != null && !headlessWebView.isRunning()) {
    headlessWebView.run();
  }
}

// ....
initWebview(WebUri("https://cdn.testhtml.com/test/html/20230801/4b6e0db8-0c4f-4a85-a664-997a4e3ed288.html"), 0);
initWebview(WebUri("https://cdn.testhtml.com/test/html/20230802/ddbc1705-ca01-454c-97b6-b20a9c16e30d.html"), 1);
initWebview(WebUri("https://cdn.testhtml.com/test/html/20230802/c60c5bf5-2e14-4a16-b572-d2bb2e7efaaf.html"), 2);
  • initWebview实现

在实现之前我们也使用常规方案进行加载webview,但是同时加载多个时还是卡顿,无法达到原生的流畅效果,仔细阅读文档后发现inappwebview6.0有一个将无头浏览器模式转换为flutter widget的功能,所以使用无头模式优先加载之后在渲染到页面中,此时发现流畅了很多。

dart 复制代码
initWebview(WebUri url, int index) {
    webviewList[index].headlessWebView = HeadlessInAppWebView(
      initialUrlRequest: URLRequest(url: url),
      initialSettings: InAppWebViewSettings(
        verticalScrollBarEnabled: false,
      ),
      onProgressChanged: (controller, progress) async {
        // 获取加载进度
        setState(() {
          webviewList[index].progress = progress;
        });
      },
      onLoadStop: (controller, url) async {
        // 当加载结束后进行页面样式的调整
        // 执行一段js,也可以使用字符串的方式,但是作为前端还是js文件更加亲切 = =!!
        // detail_html.js中的内容其实就是控制页面缩放比例之类,也可以直接在生成html时带上,这里只做参考
        await controller.injectJavascriptFileFromAsset(assetFilePath: "assets/js/detail_html.js");

        // 获取网页的宽高并通知flutter
        var bodyWidth = await controller.evaluateJavascript(source: "document.body.offsetWidth");
        var bodyHeight = await controller.evaluateJavascript(source: "document.body.offsetHeight");

        double domWidth = bodyWidth.runtimeType == double ? bodyWidth : (bodyWidth as int).toDouble();
        double domHeight = bodyHeight.runtimeType == double ? bodyHeight : (bodyHeight as int).toDouble();

        webviewList[index].width = domWidth;
        webviewList[index].height = domHeight;
        webviewList[index].convertFlag = true;

        print('=======$bodyWidth=======$bodyHeight======');

        setState(() {});
      },
    );
  }

detail_html.js内容如下:

js 复制代码
const head = document.querySelector('head')
const meta = document.createElement('meta')
const body = document.querySelector('body')
meta.setAttribute('name', 'viewport')
meta.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover')
head.appendChild(meta)

const style = document.createElement('style')
style.innerHTML = `
  * {margin: 0; padding: 0;}
  html {width: 100%; overflow-x: hidden;}
  body {display: inline-block; width: 100%; padding: 5px; box-sizing: border-box; }
  img {max-width: 100%; height: auto;}
`
head.appendChild(style)
  • 将加载好的webview转化为widget 此时准备工作已经完成:
  1. 组件的宽高
  2. webview加载完成

convertFlag: 用来控制webview加载状态,加载时显示loading

dart 复制代码
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          "HeadlessInAppWebView to InAppWebView",
          textScaleFactor: .8,
        ),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: webviewList.map((item) {
            return item.convertFlag
                ? Container(
                    padding: const EdgeInsets.all(12),
                    margin: const EdgeInsets.all(12),
                    height: item.height,
                    child: InAppWebView(
                      headlessWebView: item.headlessWebView, // 关键
                      onWebViewCreated: (controller) async {
                        item.headlessWebView = null;
                      },
                    ),
                  )
                : SizedBox(
                    width: 40,
                    height: 40,
                    child: Center(
                      child: CircularProgressIndicator(
                        value: (item.progress / 100).toDouble(),
                      ),
                    ),
                  );
          }).toList(),
        ),
      ),
    );

还可以进一步优化,判断路由动画结束后再加载,这里就不详细描述了。

由于是测试代码,还有很多优化的地方,这里主要提供了一种多webview加载的优化方案。

相关推荐
孤鸿玉1 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥8 小时前
Flutter Riverpod上手指南
android·flutter·ios
BG1 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng1 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭1 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯1 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan1 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓1 天前
Flutter Getx 的页面传参
flutter
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart