UI2Code智能生成Flutter代碼——機器生成代碼

背景

  在《UI2CODE--總體設計》篇中,咱們提到UI2Code工程的總體流程。前步圖片分析以後,咱們能夠獲得對應的DSL佈局描述。利用DSL的資訊,結合IntelliJ Plugin介面工具,面向使用者提供生成對應Flutter代碼。node

  本篇主要介紹咱們如何處理DSL的資訊,想法上便是Flutter的翻譯機。整體概念以下:算法

輸入的DSL是什麼?

  DSL作爲一種描述語言,抽象表示爲了解決某一類任務而專門設計的計算機語言。在此咱們的DSL表明圖像識別和佈局識別側的輸出,爲一JSON格式。架構

  這些資訊主要描述了這個圖層(Layer)的範圍(Frame)、是什麼樣子的類型(Type)、是什麼樣子的樣式(Styles)、含有哪些數據(Value)等等。圖層集(Layers)欄位則表明了這張視覺稿的全部圖層。框架

核心思路

  本節的目標是將DSL翻譯成目標的Flutter代碼。咱們首先須要理解的是分散的圖層間的關係,可能會有交疊、多是並列排版。知道了關係以後,需想辦法轉化成Flutter widget的視圖,根據此視圖來生產對應代碼。工具

  架構上咱們把DSL tree和Flutter tree的創建,分拆爲兩個獨立的分界。這樣比較容易定義問題,而且保持彈性。若是今天的目標語言換成Weex或是iOS UI,咱們就只須要更動代碼翻譯的模組。佈局

第一把刀:DSL tree創建

  上圖的左側表明了來源DSL的layers資料,表明者一個一個的圖層。右側是目標的DSL Tree,這棵樹的結構上明確敘述了圖層之間的包裹、交疊等關係。而且包含了某些特殊關係的節點聚合。優化

  做法上利用每一個Layer的Frame,以及所屬的類別(文字、圖像、容器),利用下面的規則組合樹的關係:ui

  1. 圖層之間的包裹關係,例如某些圖層爲容器,表明下面是能夠掛其餘節點的(這邊帶有背景屬性的容器,咱們定義稱之爲Shape
  2. 區塊式組件(Block, 如ListView/GridView)。能夠將圖層組成View item的關係
  3. 閒魚定義的組件資訊(如CI以及BI),這部份非閒魚工程能夠忽略
  4. 重複佈局(Repeat)的資訊,將相同的圖層歸類合併,目的爲簡化樹

  根據以上咱們採用了分層,由大至小的次序將Layer分羣合併。另外,在合併時layer之間彼此可能有關聯;它們可能同屬於Block,也可能同屬於某個Repeat。因此對於上面定義的Repeat、BI、Block、CI、Shape均可能有交錯的嵌套關係,這是必需要處理的部份spa

第二把刀:Flutter tree創建

  在Flutter Tree的建構中,核心概念先處理佈局。佈局的概念如剝洋蔥通常,咱們先去除四周的padding,而後以人類視覺layout的直覺先嚐試橫切分,再進行豎切分翻譯

1.先剝洋蔥去除padding

2.接著咱們的算法會先嚐試是否能夠橫切,以下圖咱們能夠切割成爲Row1/ Row2

3.針對Row1在嘗試再進行豎切,以下圖能夠獲得Column1/ Column2/ Column3

  根據以上切分的規則,咱們就能夠定義出如Row、Column、Padding的幾個節點,以及它們的Parent/ Child關係。將DSL tree同一層的節點作切分,一邊切分一邊創建Flutter node,遍歷完整顆DSL,便可獲得粗略的Flutter tree關係。

= 沒法切分時的處理

  當圖層切分不開時,這時候就要使用絕對佈局疊層的概念,這個概念在Flutter內稱之爲Stack。

  多個圖層在DSL tree的關係爲兄弟節點,根據此些圖層的Frame,咱們判斷出來它們是彼此相交的,咱們會以Z-order概念,來決定上下交疊的關係。最後,這些圖層將組成一個新Stack節點,而且產生此節點的Frames爲此些圖層覆蓋的範圍。

= 針對文字的進階處理

  基本上交疊的圖層以Stack的處理就能夠正確顯示,但在文字圖層上可能含有誤區。

11.png

  如上圖由於文字自己的上下左右是含有padding的,在咱們圖層的識別時,可能會計算出彼此的frame是交疊的,但實際上UI但願它們並非Stack關係。

sample.png

  爲了解決這個問題,咱們引入了一個oriFrame的概念,用文字最原始的像素當作是oriFrame。因此遇到爲文字的圖層時,咱們會先判斷自己的oriFrame是否交疊,若是是的話才採用Stack切割,不然就以此oriFrame對原始的frame作修正。

文字還有什麼特性?

  另外,由於文字的內容一般是動態的,因此擁有了」所見不必定爲所得」的特性。這些特性主要包含了是否該換行、內容區域是否能夠拉伸、文字Padding等,這些特性都會影響到咱們的佈局。

  如下圖爲例,咱們在處理Layout時肉眼很明顯能夠知道這些特徵。文字的行數咱們能夠以視覺稿當作最大顯示範本,文字區域的寬度部分,則須要特別判斷哪些區域是能夠被拉伸的。

111.png

確立文字範圍

  在決定拉伸對象以前,咱們須要定義哪些widget是將內容完整顯示,不能被拉伸的:如圖片、Container容器、Stack區域、Component組件

  接著處理的流程以下:

  1. 首先判斷全部Child內是否有多行文字或寬度固定的文字,若是是的話針對其處理。須要加上Flex。
  2. 若無以上的情況,則判斷Child間的Padding關係

    • 若是可拉伸的widget的Padding大於平均值的個數有多個,則這些都加上Flex

      • 若是隻有單個時,則找尋最大Padding的widget(使用分羣拉伸算法)
  3. 最後,但當Row裏面存有拉伸的情況時,須要把Row的最後一個child加上Right padding,不然拉伸元素會填滿父容器。

分羣拉伸算法:這個算法的目的是找到最佳拉伸的對象。咱們的思考上將Widget作分組,分組後判斷總體的Alignment(如左右對齊)或是拉伸關係。若在拉伸情況下,判斷適合讓哪一個組別拉伸,在進一步判斷適合讓組別的內部元件拉伸。

  舉例以下爲一個Row排列的控件,其中排列爲Image、CI、Text一、Text二、Text3:
group3.png

  依據Widget之間的距離,在上圖分爲了Group1及Group2兩個羣體。先以Group1判斷是否存在可拉伸的對象, 接著才判斷Group2。因此這5個Widget分別獲得了3, 2, 1, 4, 5的優先級。以本例而言,Text1爲最高優先,並且其爲可拉伸的,故決定將Flex屬性加於此。

  在Expanded的處理上,是咱們目前遇到最大的困難點,甚至人工判斷均可能有歧義。上面的規則是咱們概括出衆多視覺稿的通解,但不能100%徹底解決問題。因此這部份判斷錯誤的部分,咱們期待在Plugin的交互中使用人工解決。

= 判斷Alignment優化

  以上的處理已經能夠正確生成Flutter tree,可是咱們想進一步地將Flutter代碼更加優雅。在此咱們針對了三種元件的Alignment作了處理,分別是Container、Row、Column,其概念都是分析內部元件的padding關係,決定爲居左、居中、或是居右對齊。

  舉例如Column內部的children咱們去判斷左右的padding是否相等。如果則移除其padding,而且加上crossAxisAlignment爲center。

column.png

  針對Row/ Container咱們則會判斷crossAxisAlignment(垂直方向)以及mainAxisAlignment(水平方向)。水平部份,這邊咱們採用更精細的方法,咱們利用歐式距離創建一個非監督算法,計算views是更爲接近哪個(居左、居中、居右)。算法這邊先不詳述,以後再以篇幅介紹。

最後:生成Flutter代碼

  通過前面的步驟後,最終咱們產生了一個Flutter Tree。生成時在節點的定義上,咱們分爲了兩種,分別是View與Layout,以是否能夠擁有Child爲區別。如下是咱們針對Flutter Tree所定義的部份類別:

333.png

  在節點的定義中,皆存儲了各節點的Parent、Child屬性。根據這些關係,咱們定義每一個節點的代碼樣板,例如FColumn對應的樣板爲:

 

 new Column(
#{alignment}, children: <Widget>[ #{children}, ]

), 

  最後咱們以Root widget開始遍歷整顆樹,將每一個節點所生成的Flutter代碼結合,這樣咱們就能夠獲得整個Widget tree的代碼了。

數據分離

  爲了更好的重複利用生成代碼,咱們把生成的代碼和數據再進一步作分離。分離後輸出分爲代碼區以及Data model數據區:

datamodel.png

  咱們切割這些區域的目的爲簡化Widget tree直觀上的代碼複雜度,以及將數據抽離,讓資料可由外部呼叫傳入,以達成動態性。

總體架構回顧

  總合以上的概念,工程的細部架構以下:
11.png

  前面所說的針對文字以及Alignment的處理,在這邊咱們設計了一個工廠模式,如上圖中通過Flutter Tree Builder後,咱們能夠去遍歷整顆Widget tree,在工廠中判斷判斷符合條件的規則,通過處理去震盪優化本來的Widget tree。在這邊將來咱們能夠不斷地加上合適的規則,讓Widget tree更加優化。

  總體架構使用靜態分析的方法,讀到此各位可能會有疑問:一些如動態的事件、View的Visibility、Input輸入文字框等怎麼處理?因爲這些動態性在靜態分析下沒法解決,因此咱們加強了Plugin上的編輯性,使用者只要勾選某些屬性,即會在生成代碼時自動判斷,在Flutter自動增長對應的邏輯。以彌補靜態圖沒法處理的問題。

  因爲UI的靈活性高,十我的寫的代碼可能有十種不一樣風格。而且在分析上游的UI2DSL,以及Flutter代碼的翻譯,某些部份的精確性取決於咱們的樣本的認知,是否可以在有限的樣本內觀查出泛化的定律,分析上仍是存有不少挑戰性。

結合落地業務

  在整個UI2CODE的效果中,大約七成以上的頁面均可以正確分析出來,剩下的是一些小細節如文字的處理等,基本上咱們工具都可以將大框架的處理好,使用者可能只需微小的調整。

  UI2CODE案子在內部團隊上線後,已經在閒魚APP內的"玩家頁面"採用了自動化生成的代碼。在採用自動化工具後,大約減小了三分之二的UI開發時間(因初期還在熟悉工做流程,將來相信能夠更快速)。同時,若在客戶端大量採用咱們工具,還可讓團隊的代碼結構有一些的規範,讓生成工具來規範Widget UI以及Data Binding的框架,一致性以及後續的維護,相信是一個很大的誘因。

  而且閒魚團隊近期計畫開發一款新的APP,在初期時可以快速開發UI,也將採用咱們的工具。指望有更多的業務和經驗積累。

後續計畫

  近期咱們推出了初版UI2CODE,先計畫於內部團隊使用,利用使用的經驗,讓咱們在疊代之下不斷提升準確性。而且,咱們正在調研結合NLP以及AST(語法樹)的可能性,但願可以產出更有質量的代碼。

  咱們也指望將來能將此工具開放於Flutter community,對於推進整個Flutter技術有所推動。但願能讓更多人跟咱們一塊兒找尋更有效率的寫代碼方法,若是有任何想法歡迎與咱們交流,咱們也持續不斷地在進化工具中,謝謝各位的閱讀!

做者:閒魚技術-上葉,餘晏

原文連接

​本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索