您好,登錄后才能下訂單哦!
本篇內容主要講解“flutter_mp的實現原理以及通過flutter_mp轉換并運行在小程序端的效果”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“flutter_mp的實現原理以及通過flutter_mp轉換并運行在小程序端的效果”吧!
原理簡介
雖然還有諸多功能未完成,我們先來談談整個flutter_mp的實現原理。篇幅原因,下面我們將只對flutter_mp幾個重要的部分進行簡單說明。
先看下flutter_mp的實際效果:
Flutter版官方layout樣例:
通過flutter_mp轉換并運行在小程序端效果
聲明式UI的處理
Flutter是聲明式UI框架,聲明式UI只需要向框架描述UI長什么樣子而不用關心框架具體的實現細節,具體到Flutter,上層的UI描述使用底層的skia圖形引擎處理就是原生Flutter,而把底層處理換成html/css/canvas就是flutter_web,flutter_mp則是探索在類小程序上對這些UI描述的處理。
我們看一個最簡單例子
var x = 'Hello World' Center( child: Text(x) );
對于上面的UI結構,我們只需要在小程序的wxml文件里,用如下的結構對應就OK了。
// wxml部分 <Center> <Text>{{x}}</Text> </Center> // js 部分 Component({ data: { x: 'Hello World' } })
雖然實際的結構要比上面的情況復雜的多,不過通過上面簡單的例子,我們知道起碼要做兩個事情:
我們需要根據Flutter代碼生成相關小程序wxml模版文件 收集wxml渲染需要的數據,放置到小程序組件的data字段。
wxml結構生成
我們知道小程序是無法動態操作節點的,wxml結構需要預先生成,所以Flutter運行在小程序之前,會存在一個編譯打包階段,這個階段會遍歷Dart代碼, 根據一定規則生成wxml文件(編譯階段還會做下文將要提到的另外一個重要事情 --- 把Dart編譯為js)。
具體的,我們首先會將Dart源碼處理為可分析的AST結構,AST是源代碼的樹型表示結構。然后我們深度遍歷這份AST語法樹結構,生成目標wxml,整個過程如下:
構建wxml結構的難點在于:Flutter不僅是聲明式UI還是“值UI”,什么叫“值UI”?簡單來說,Flutter把UI看成是一個普通的值,類似于字符串,數字一樣的值,既然是一個普通的值,就可以參與所有的控制流程,可以是函數的返回值也可以是函數參數等等。而小程序的wxml雖然也是聲明式UI,卻不是“值UI”,wxml更加像模版,更加的靜態。怎么用靜態的wxml表達動態的“值UI”是構建wxml結構的關鍵所在。
看個例子
Widget getX() { if (condition1) { return Text('Hello'); } else if (condition2) { return Container( child: ... ); } else if (condition3) { return Center( child: ... ); } ... } Widget x = getX(); Center( child: x // < --- 如何處理這里的 x?? );
這里的child: x x是一個動態值,它的具體值需要在運行階段才能確定,它可能是任意的Widget,如何在靜態的wxml上處理這里動態的x?受Alita框架的啟發,這里主要是借助于小程序template的動態性(template的is屬性可以接受變量值)。有如下幾步:
1、首先在遍歷Dart源碼AST結構的時候,會把每一個獨立完整的“UI值”片段,對應到wxml的template, 比如上文 getX 里面的UI
<template name="template001"> <text>Hello</text> </template> <template name="template002"> <Container>...</Container> </template> <template name="template003"> <Center>...</Center> </template>
2、在遇到 類似x 這種動態值的時候,固定的會生成一個template占位
<template name="template004"> <Center> <template is="{{templateName}}" data="{{...templateData}}"/> </Center> <template name="template003">
3、在運行階段,會根據getX
函數的運行結果來決定x映射的“UI值”,如果getX里面condition1為true,那么這里的templateName的值就是template001。具體的數據計算收集工作,參考下面要的 “渲染數據收集”過程。可以看出flutter_mp處理“值UI”方式,完全參考了Alita。
渲染數據收集
wxml結構的生成是在編譯階段就完成了,與它不同渲染數據是運行時的信息,隨時會根據setState而改變。那么我們怎么收集出我們需要的渲染數據呢?
如果我們還是順著Flutter的架構圖,很難插入我們收集的鉤子函數,另外Flutter的這個架構對于小程序來說太重了,下圖紅框里的這些過程對于小程序的渲染來說并不必要。最后由于最終的代碼會被轉化為js,而Flutter本身依賴的庫里面很多是不支持轉化js的,比如dart:ui等等。
所以我們實現了一個極簡極簡的Flutter小程序版本mini_flutter,在編譯期我們會把所有對Flutter庫的引用替換為mini_flutter, mini_flutter只存在到上圖的Rendering階段,這個Rendering的實現也是為小程序定制的, 在運行時期Rendering不斷收集Widgets的信息。最終生成一個UI描述的JSON結構,這個結構就包含了上文所說的templateName, templateData,UI描述將會被下層小程序獲得,用來渲染小程序UI,架構圖如下:
Dart/JS:轉化與互操作
Flutter的開發語言是Dart,而小程序的運行環境是瀏覽器,所以我們還需要把Dart編譯為JavaScript代碼。
在上文的編譯打包階段也提到這一點,這個過程主要是使用了Dart提供的dart2js工具,不過,針對小程序環境,生成的js代碼仍需要做一些適配,另外雖然都是JS代碼,dart2js生成的js和小程序原生js的運行環境卻是隔離的,也就是說它們是不能共享變量,方法等等,它們各自在本身的"域"里執行。
這帶來兩個問題:
1、Widget 初始化 或者setState更新,生成的UI描述JSON,如何傳遞給小程序"域"呢? 2、相關渲染回調,事件的都發生在小程序"域",這些信息如何傳遞給Dart?
總結一下:Dart(最終會編譯為JS)與小程序原生JS如何互操作?
解決這個問題主要是借助dart:js, package:js這兩個庫:
Dart操作JS
<template name="template004"> <Center> <template is="{{templateName}}" data="{{...templateData}}"/> </Center> <template name="template003">
這樣當Dart代碼調用stringify方法的時候,實際上會執行window.JSON.stringify方法
JS操作Dart
// dart注冊 void main() { context['dartHi'] = () { print('dart hi!'); }; }
// js 調用 window.dartHi()
這里只是簡單說明Dart與JS的互操作,另外由于小程序的運行環境是閹割以后的瀏覽器環境,flutter_mp的實現還稍有不同。
總之,Dart與JS是可以互操作的,這樣就打通了上層Flutter環境和下層小程序環境。
布局系統
Flutter的布局系統不同與css,但是和css頗相似。
在上文提到的Rendering階段,會根據Widget的布局屬性,類別,約束條件生成一個等效的css樣式。注意這里邊界約束是上下文相關的。比如一個沒有寬高的Container實際大小,不僅和子元素相關,還和父元素傳遞過來的邊界約束條件相關,這個其實是比較麻煩的,能不能把Flutter的Widget屬性,邊界約束完全用css表達,我們還在尋求有效的方案。
總結
和flutter_web一樣,完全把Flutter所有特性渲染到小程序上是不可能的,一般我們覺得應該是部分頁面,部分功能需要運行在小程序上,這樣使用flutter_mp才是有意義的。
到此,相信大家對“flutter_mp的實現原理以及通過flutter_mp轉換并運行在小程序端的效果”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。