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时重新加载了数据
相关推荐
前端 贾公子4 小时前
《Vuejs设计与实现》第 16 章(解析器) 上
vue.js·flutter·ios
tangweiguo0305198713 小时前
Flutter 数据存储的四种核心方式 · 从 SharedPreferences 到 SQLite:Flutter 数据持久化终极整理
flutter
0wioiw014 小时前
Flutter基础(②④事件回调与交互处理)
flutter
肥肥呀呀呀14 小时前
flutter配置Android gradle kts 8.0 的打包名称
android·flutter
吴Wu涛涛涛涛涛Tao18 小时前
Flutter 实现「可拖拽评论面板 + 回复输入框 + @高亮」的完整方案
android·flutter·ios
星秋Eliot2 天前
Flutter多线程
flutter·async/await·isolate·flutter 多线程
农夫三拳_有点甜2 天前
Flutter Assets & Media
flutter
林间风雨2 天前
flutter项目 -- 换logo、名称 、签名、打包
flutter