ios flutter_echarts 不在当前屏幕 白屏修复

项目里一直在用flutter_echarts作为图表展示。 今天发现一个bug,ios如果echarts不在当前屏幕,则echarts会显示白屏幕。

然后在github上发现有人有相同的问题: github.com/entronad/fl...

令人懊恼的是这个项目已经不再维护了。没办法,只有自己动手丰衣足食啦。

重现这个问题可以将Echarts放在ScrollView里,并且确保它不在当前屏幕。我看这个issues里说可以刷新两次。但治标不治本偶尔仍然会出现白屏。

由于flutter_echarts是通过webview展示的,刚开始不管如何修改都没办法。后来将内置的webview修改成inappwebview。发现ios里inappwebview会回调 onContentSizeChanged函数。将window size从0设置成具体的值。我猜测是因为ios webview如果判断是离屏的则webview的size是0,这个时候去初始化图表控件尺寸就会出问题。解决办法也很简单,就是当回调 onContentSizeChanged时再去初始化。

以下为修改后的代码:

dart 复制代码
import 'dart:convert';
import 'dart:io';
import 'package:bdh_smart_agric_app/utils/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_echarts/echarts_script.dart' show echartsScript;

/// <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, target-densitydpi=device-dpi" /><style type="text/css">body,html,#chart{height: 100%;width: 100%;margin: 0px;}div {-webkit-tap-highlight-color:rgba(255,255,255,0);}</style></head><body><div id="chart" /></body></html>

/// 'data:text/html;base64,' + base64Encode(const Utf8Encoder().convert( /* STRING ABOVE */ ))
const htmlBase64 =
'PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PG1ldGEgY2hhcnNldD0idXRmLTgiPjxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAsIHVzZXItc2NhbGFibGU9MCwgdGFyZ2V0LWRlbnNpdHlkcGk9ZGV2aWNlLWRwaSIgLz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPmJvZHksaHRtbCwjY2hhcnR7aGVpZ2h0OiAxMDAlO3dpZHRoOiAxMDAlO21hcmdpbjogMHB4O31kaXYgey13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LDApO308L3N0eWxlPjwvaGVhZD48Ym9keT48ZGl2IGlkPSJjaGFydCIgLz48L2JvZHk+PC9odG1sPg==';

 
class InAppWebViewEcharts extends StatefulWidget {

    InAppWebViewEcharts(
        {Key? key,
        required this.option,
        this.extraScript = '',
        this.onMessage,
        this.extensions = const [],
        this.theme,
        this.captureAllGestures = false,
        this.captureHorizontalGestures = false,
        this.captureVerticalGestures = false,
        this.onLoad,
        this.onWebResourceError,
        this.reloadAfterInit = false})
        : super(key: key);

    final String option;
    final String extraScript;
    final void Function(String message)? onMessage;
    final List<String> extensions;
    final String? theme;
    final bool captureAllGestures;
    final bool captureHorizontalGestures;
    final bool captureVerticalGestures;
    final void Function(WebViewController)? onLoad;
    final void Function(WebViewController, Exception)? onWebResourceError;
    final bool reloadAfterInit;

    @override
    _EchartsState createState() => _EchartsState();
}



class _EchartsState extends State<InAppWebViewEcharts> {
    InAppWebViewController? inAppWebViewController;
    String? _currentOption;
    String? html;

    @override
    void initState() {
        super.initState();
        _currentOption = widget.option;
        html = utf8.fuse(base64).decode(htmlBase64);
    }


    void init() {
        final extensionsStr = widget.extensions.isNotEmpty ? widget.extensions.reduce((value, element) => value + '\n' + element) : '';

        final themeStr = this.widget.theme != null ? '\'${this.widget.theme}\'' : 'null';

        inAppWebViewController?.evaluateJavascript(source: '''
            $echartsScript
            $extensionsStr
            var chart = echarts.init(document.getElementById('chart'), $themeStr);
            ${widget.extraScript}
            chart.setOption($_currentOption, true);
        ''').then((v) {
            Log.d("init data success $v");
        });
    }

    Set<Factory<OneSequenceGestureRecognizer>> getGestureRecognizers() {
        Set<Factory<OneSequenceGestureRecognizer>> set = {};
        if (widget.captureAllGestures || widget.captureHorizontalGestures) {
            set.add(Factory<HorizontalDragGestureRecognizer>(() {
                return HorizontalDragGestureRecognizer()
                ..onStart = (DragStartDetails details) {}
                ..onUpdate = (DragUpdateDetails details) {}
                ..onDown = (DragDownDetails details) {}
                ..onCancel = () {}
                ..onEnd = (DragEndDetails details) {};
            }));
        }

        if (widget.captureAllGestures || widget.captureVerticalGestures) {
            set.add(Factory<VerticalDragGestureRecognizer>(() {
                return VerticalDragGestureRecognizer()
                ..onStart = (DragStartDetails details) {}
                ..onUpdate = (DragUpdateDetails details) {}
                ..onDown = (DragDownDetails details) {}
                ..onCancel = () {}
                ..onEnd = (DragEndDetails details) {};
            }));
        }
        return set;
    }

    void update() {
        _currentOption = widget.option;
        inAppWebViewController?.evaluateJavascript(source: '''
            try {
                chart.setOption($_currentOption, true);
            } catch(e) {

            }
        ''');
    }

    void loadData() {
        inAppWebViewController?.loadData(data: html ?? "");
    }

    @override
    void didUpdateWidget(InAppWebViewEcharts oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (oldWidget.option != widget.option) {
            update();
        }
    }

    @override
    void dispose() {
        // inAppWebViewController?.dispose();
        inAppWebViewController = null;
        super.dispose();
    }

    @override
    Widget build(BuildContext context) {
        return InAppWebView(
            initialSettings: InAppWebViewSettings(
            allowsInlineMediaPlayback: false,
            allowsAirPlayForMediaPlayback: false,
            allowsPictureInPictureMediaPlayback: false,
            allowsLinkPreview: false,
            mediaPlaybackRequiresUserGesture: false,
            underPageBackgroundColor: Colors.transparent,
            transparentBackground: true,
            verticalScrollBarEnabled: false,
            horizontalScrollBarEnabled: false,
            disableHorizontalScroll: true,
            disableVerticalScroll: true,
            disallowOverScroll: true,
            displayZoomControls: true,
            clearCache: true,
            supportZoom: false,
            builtInZoomControls:false,
            geolocationEnabled:false,
            cacheEnabled: false),
            onWebViewCreated: (controller) async {
            inAppWebViewController = controller;
                loadData();
            },
            onContentSizeChanged: (controller, oldContentSize, newContentSize) {
                loadData();
            },
            onZoomScaleChanged: (controller, oldScale, newScale) {
                loadData();
            },
            onLoadStop: (controller, url) {
                init();
            },
            gestureRecognizers: getGestureRecognizers(),
      );
    }
}

上面代码的修改点有:

  1. 将flutter_webview替换成了inappwebview
  2. 在onContentSizeChanged 和 onZoomScaleChanged时重新加载了数据
相关推荐
不爱吃糖的程序媛7 分钟前
Flutter-OH 升级指导
flutter
恋猫de小郭2 小时前
Android 禁止侧载将正式实施,需要等待 24 小时冷静期
android·flutter·harmonyos
FFF-X3 小时前
解决 Flutter Gradle 下载报错:修改默认 distributionUrl
flutter
程序员Ctrl喵1 天前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难1 天前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 天前
flutter列表中实现置顶动画
flutter
始持1 天前
第十二讲 风格与主题统一
前端·flutter
始持1 天前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持1 天前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter