響應式 Echarts Flutter 組件

配置擴展javascript

Echarts 有很豐富的 擴展 ,包括圖表、地圖、WebGL 等,在 Web 開發中,它們能夠以腳本的形式引入代碼,從而擴展 Echarts 的功能。爲知足開箱即用, flutter_echarts 內置了最新版的 Echarts 腳本,無需額外引入,同時提供了 extensions 參數,方便使用者引入所需的擴展腳本。 extensions 參數類型爲字符串數組,使用者可直接拷貝腳本做爲字符串到源碼中,避免了文件讀寫操做和繁瑣的 asset 目錄。html

組件參數

封裝功能性的組件,其易用性每每比完備性更重要,要讓任意水平的開發者都能開箱即用。Echarts 自己在設計時也是遵循易用性的原則,儘量的將全部配置工做,交給 option 這一個參數 去完成( 詳見論文 )。所以 flutter_echarts 在設計時也儘可能簡化組件參數:java

optiongit

Stringgithub

字符串形式的 JavaScript Echarts Option。Echarts 圖表主要就是經過這個參數配置的。你能夠經過 dart:convert 中的 jsonEncode() 來轉換 Dart 對象類型的數據:web

source: ${jsonEncode(_data1)},
複製代碼

因爲 JavaScript 沒有''' 符號,你可使用它來包裹字符串,以省掉一些引號的轉義:apache

Echarts(
  option: ''' // option string ''',
),
複製代碼

extraScriptjson

Stringredux

Echarts.init() 和任意 chart.setOption() 之間執行的 JavaScript 腳本。在組件中咱們已經內置了一個 名爲 Messager 的 JavascriptChennel,因此你可使用這個標識符來進行 JavaScript 向 Flutter 的通訊:數組

extraScript: ''' chart.on('click', (params) => { if(params.componentType === 'series') { Messager.postMessage('anything'); } }); ''',
複製代碼

onMessage

void Function(String)

處理 extraScriptMessager.postMessage() 發送的消息的函數。

extensions

List

從 Echarts 擴展中拷貝的腳本字符串組成的數組,好比各類組件、WebGl、語言等。能夠從 這裏 下載。將它們做爲原始字符串(raw string)引入:

const liquidPlugin = r''' // copy from liquid.min.js ''';
複製代碼

目前僅有以上 4 個參數,控制更新等由內部機制完成,爭取作到用起來就像個簡單的表現型 StatelessWidget,只要使用者熟悉 Echarts 自己而不須要額外的學習成本。

固然,若是有建議或要求,請發起 issue

源碼解析

html 的加載

對於跨平臺的開發方案,因爲不一樣的底層操做系統,文件資源目錄一直是個麻煩的事情,在 React Native 中有時甚至必須手動將 html 拷貝到 Android 對應的目錄。Flutter 雖然有了完善的 asset 系統,但也須要額外的依賴和配置。直接將本地 html 做爲源碼中的文本字符串加載是解決這些問題的好辦法,webview_flutter 的官方示例也比較推薦用這種辦法處理本地 html 。

所以咱們將模板 html 、Echarts 腳本、擴展腳本、初始化邏輯等在組件初始化時拼接成字符串,做爲 uri 資源供 WebView 加載:

@override
  void initState() {
    super.initState();
    _htmlBase64 = 'data:text/html;base64,' + base64Encode(
      const Utf8Encoder().convert(_getHtml(
        echartsScript,
        widget.extensions ?? [],
        widget.extraScript ?? '',
      ))
    );
    _currentOption = widget.option;
  }
  
  ...
  
  @override
  Widget build(BuildContext context) {
    return WebView(
      initialUrl: _htmlBase64,
      
      ...
    );
  }
複製代碼

值得注意的是,做爲 uri 資源的字符串,是有一些特殊字符限制的,所以加載時咱們將字符串轉爲 Base64 編碼。

這裏有一個小技巧因爲 JavaScript 中沒有 ''' 這個符號,所以在 Dart 中用 ''' 包裹 JavaScript 腳本字符串能夠減小不少轉義工做。

圖表更新

響應式更新基本的實現機制就是在 State.didUpdateWidget 方法中經過setOption 通知 Echarts 更新圖表:

void update(String preOption) async {
    _currentOption = widget.option;
    if (_currentOption != preOption) {
      await _controller?.evaluateJavascript(''' chart && chart.setOption($_currentOption, true); ''');
    }
  }

  @override
  void didUpdateWidget(Echarts oldWidget) {
    super.didUpdateWidget(oldWidget);
    update(oldWidget.option);
  }
複製代碼

這其中比較麻煩的是在組件剛剛初始化的時候。

咱們知道 WebView 加載 html 和外部數據的獲取都是異步的,事先並不知道誰會先完成。WebView 初始化時生命週期的順序是:

onWebViewCreated --> 加載html --> onPageFinished
複製代碼

而 WebViewController 通常是在 onWebViewCreated 中獲取的。換言之,當組件拿到 WebViewController 時,並不能確保 WebView 中的 html 已經加載完成,因此 didUpdateWidget 不能僅依據是否已經拿到 WebViewController 決定是否能夠更新了。

解決辦法是將「外部數據更新時更新圖表」解耦爲「外部數據更新時更新內部 _currentOption 」 和 」當須要更新圖表時調用 _currentOption 「兩步,從而確保 html 加載完成前獲取的數據也能被記錄更新:

String _currentOption;
  
  void init() async {
    await _controller?.evaluateJavascript(''' chart.setOption($_currentOption, true); ''');
  }

  void update(String preOption) async {
    _currentOption = widget.option;
    ...
  }
  
  @override
  Widget build(BuildContext context) {
    return WebView(
      ...
      onPageFinished: (String url) {
        init();
      },
      ...
    );
  }
複製代碼

內置信道

webview_flutter 提供了 javascriptChannels 參數,能夠設置多路命名信道。不過爲了使不熟悉 webview_flutter 的使用者也能快速上手, flutter_echarts 並無暴露這個參數來管理通訊,而是內置創建了一個名爲「 Messager 」的信道:

@override
  Widget build(BuildContext context) {
    return WebView(
      ...
      javascriptChannels: <JavascriptChannel>[
        JavascriptChannel(
          name: 'Messager',
          onMessageReceived: (JavascriptMessage javascriptMessage) {
            widget?.onMessage(javascriptMessage.message);
          }
        ),
      ].toSet(),
    );
  }
複製代碼

使用者若是有多種事件須要通訊,能夠像 redux action 那樣進行設置:

chart.on('click', (params) => {
  if(params.componentType === 'series') {
    Messager.postMessage(JSON.stringify({
      type: 'select',
      payload: params.dataIndex,
    }));
  }
});
複製代碼
相關文章
相關標籤/搜索