設計稿(UI 視圖)自動生成代碼方案的探索
1 背景
設計稿(UI 視圖)轉代碼是前端工程師日常不斷重複的工作,這部分工作複雜度較低但工作佔比較高,所以提升設計稿轉代碼的效率一直是前端工程師追求的方向之一。此前,前端工程師嘗試過將業務組件模塊化構建成通用視圖庫,並通過拖拽、拼接等形式搭建業務模塊,從而實現視圖複用,降低設計稿轉代碼的研發成本。但隨着業務的發展和個性化的驅動,通用視圖庫無法覆蓋所有應用場景,本文提出了一種設計稿自動生成代碼的方案。
目前,業內主流的代碼生成方案有兩種,一種是通過訓練神經網絡,從圖片或草圖直接生成代碼,以微軟 sketch2json 爲代表;另一種是基於 Sketch 源文件,從中解析出圖層信息轉化成 DSL 並生成代碼,以 imgCook 爲代表。
經過實踐,我們發現第一種方案基於神經網絡的代碼生成算法雖然簡單粗暴,但複雜層佈局的準確率較低、可解釋程度不高導致後續無法持續優化。方案二中 Sketch 源文件信息量豐富、算法自定義程度高、優化空間大。因此,我們調研了業界基於 Sketch 的代碼自動生成方案(已對外公佈或者開源),發現了一些不足並嘗試解決,下面從算法準確率、代碼可讀性、研發流程覆蓋度等方面做一下對比(該對比結果僅考察業界方案對我們自己業務的適用性,實際結果可能存在差異):
-
算法準確率方面:淘寶 imgCook 支持基於 AI 的組件識別,不支持成組佈局,準確率中等(從官網瞭解到可以識別循環佈局,但不能識別出測試樣本中的循環佈局),58 Picasso 僅支持原始組件的識別,複雜組件生成錯誤較多,不支持成組 / 懸浮 / 循環佈局,準確率較低。
-
代碼可讀性方面:淘寶 imgCook 在生成佈局時,測試樣本中圖層重疊區域使用到了基於根佈局的絕對定位方式,不符合 RD 預期,可讀性一般,而我們的方案使用相對定位方式,可讀性較好。
-
研發流程覆蓋度方面:淘寶 imgCook 從 RD 視角構建了一個 IDE,支持在 IDE 中完成樣式調整、邏輯綁定;而我們的方案從產研協作視角出發,支持數據、邏輯、埋點的可視化配置及上線。
2 方案介紹
如圖所示,配置平臺主要分成三塊包括:設計稿轉視圖樹(UI2DSL)、視圖樹轉代碼(DSL2Code)、以及業務信息綁定,下面簡單介紹一下每一塊的作用。
-
設計稿轉 DSL 視圖樹(UI2DSL):將設計稿轉化成平臺無關的 DSL 視圖樹。
-
視圖樹轉代碼(DSL2Code):將 DSL 視圖樹轉化成基於 Flex 佈局的 MTFlexBox 靜態代碼。
-
業務信息綁定:提供可視化配置工具,支持 MTFlexBox 靜態代碼綁定後臺數據、業務邏輯、以及曝光 / 點擊等埋點邏輯。
2.1 設計稿轉視圖樹(UI2DSL)
UI2DSL 主要經歷以下四個步驟:
2.1.1 設計稿導入
在日常開發過程中,我們接觸比較多的組件有按鈕、標題、進度條、評分組件等,但是 Sketch 數據源中並沒有這些組件只有圖層信息,圖層是設計師在設計 UI 視圖時用到的視圖控件。組件與圖層的對應關係是一對多的關係,圖層在 Sketch 數據源中的表現形式如下圖中的 JSON 數據結構所示,描述了圖層的座標、大小等信息,後續佈局生成就是基於對圖層的切割來實現的。
[
{
"class_name":"MSTextLayer",
"font_face":"PingFangSC-Medium",
"font_size":13.44,
"height":36.5,
"index":8,
"line_height":18.24,
"name":"恆都民生精選豬小排帶骨400g±25g",
"object_id":"EF55F482-A690-4EC2-8A6E-6E7D2C6A9D91",
"opacity":0.9000000357627869,
"text":"恆都民生精選豬小排帶骨400g±25g",
"text_align":"left",
"text_color":"#FF000000",
"type":"text",
"width":171.8,
"x":164.2,
"y":726.7
},
//......
]
2.1.2 組件識別
從上面的數據源可以看出,圖層有圖片、文字、矩形等基本類型,在組件識別這一步圖層需要被轉化成文字 / 圖片 / 進度條 / 評分組件 / 價格組件 / 角標等日常開發使用的組件類型。但是,目前我們的進展還停留在只能將圖層識別爲文字或者圖片的階段,後續我們將接入淘寶開源的 pipcook 框架,基於神經網絡算法進行更加豐富的組件類型識別。
2.1.3 可視化干預
設計稿作爲輸入源是設計稿自動轉代碼的基礎,這對設計稿的設計規範要求較高。但在實踐中,我們發現設計師會利用 Sketch 中的基本圖形(每個圖形最終形成數據源中的一個圖層)疊加來描述一個組件的視覺效果,因此設計稿中不可避免會出現冗餘圖層的問題,干擾 DSL 的生成。
雖然我們也嘗試了利用自動化的手段刪除冗餘圖層,但對於算法不能識別的部分(例如:圖片上有一個文本圖層,但是實際情況中文本是顯示在圖片裏的,這個時候無法從算法層面決定是否刪除文本),仍然需要靠人工進行圖層刪除、合併等,否則無法正常生成 DSL。設計稿主要有以下幾類問題。
圖層未合併
上圖是從設計稿解析出來的結果,可以發現在 “美團優選” 文字上方的圖片中有很多紅色的矩形框(每個矩形框是一個單獨的圖層),而算法預期的輸入是一個圖層,因此需要在算法處理前將多個圖層合併成一個圖層,右側的三張圖也有類似問題。我們與設計同學進行過溝通,設計同學表示願意在產出設計稿之前將圖層進行合併,但由於目前無法提供檢測機制(圖層合併是否有遺漏無法自動檢測出來),也就無法徹底避免圖層未合併的問題。
圖層位置交叉
實踐中發現當設計稿中不同字體 / 大小 / 顏色的文字排列在一起時,解析出來的圖層信息往往會出現重疊的情況,由於 DSL 視圖樹算法依賴位置來確定不同組件的約束關係,因此位置的交叉會對算法準確度造成較大的影響。
複雜背景圖層
上圖中紅色背景是由 2 個圖層(2 個藍色矩形框)拼接形成的,左圖上的藍色圖層是純色,右圖上的藍色圖層是漸變色,在兩個圖層未合併的情況下,算法生成的代碼將會出錯。
上面提出的問題,通過約束設計師來達到設計稿的規範化,難度較大,所以我們提供了可視化干預工具。下面對上述問題做一個簡單的總結:
-
問題一:圖層未合併問題肉眼很容易識別出來,利用工具將冗餘圖層進行快速合併刪除即可。
-
問題二:圖層交叉問題肉眼不易識別,因此我們提供了檢測工具,基於檢測工具可以對設計稿中的交叉問題快速修復。
-
問題三:複雜背景問題肉眼不易識別,暫時也沒有有效的檢測工具,用戶可以採用邊干預邊生成的方式生成 DSL。
可視化干預是重要的一環,經過可視化干預,將不標準的設計稿轉化爲標準的圖層信息後再輸入給算法,可以極大地提升算法的準確率。這裏我們和 imgCook 的處理方式有一個區別:imgCook 在引入了閾值處理等算法後(更智能,出錯概率更大),可視化干預能力主要體現在事後,而我們在生成 DSL 之前允許用戶對圖層進行干預,在干預時用戶面對的是直觀的圖層信息,可以有效降低工具的使用門檻(更穩定,效果更好)。
2.1.4 視圖樹生成
將扁平的數據源轉化爲樹狀結構的 DSL,這個過程如果是人腦來做會怎麼思考呢?先確定佈局的整體結構是行佈局或者列布局,然後再確定局部區域應該是什麼佈局結構,最後組裝起來形成視圖樹。這個過程與遞歸算法類似,因此我們採用了遞歸算法作爲算法的主框架,同時引入了 “橫豎切割 + 佈局結構 + 模型評估” 三大利器。
利器一:橫豎切割
生成 DSL 時採用了整分的思路,即將大布局不斷的切分成小布局,下面以動畫的形式看一下簡化過的 DSL 生成過程:
將設計稿一部分區域視爲一個子區域,最開始的時候子區域和整個模板的面積一樣大,基於圖層的位置、大小信息,計算每個圖層的上 / 下 / 左 / 右邊緣座標與其他圖層的相對關係,就可以尋找到切割點(如上圖中紅色箭頭所指的位置)。接下來依據切割點,將子區域切割成更小的子區域,在切割的過程中如果切割點是橫向的,則生成列布局;如果切割點是縱向的,則生成行佈局。通過不斷的切割子區域得到更小的子區域,直到所有的子區域只剩下圖片或者文本等不可切割的圖層,這樣就可以生成完整的 DSL 視圖樹了。爲了方便讀者理解,圖例中只演示了行佈局、列布局的切分過程,實際情況還包含了其他佈局類型,會要複雜許多。
這裏還要注意一個問題,當有 3 個切割點時,我們選擇了直接將子區域切割成 4 個子區域,實際上我們可以只選擇 1 個切割點進行切割,也可以選擇 2 個切割點進行切割,當有 N 個切割點時,實際上存在(N 的階乘 + 1)種切割方式,具體選擇哪種切割方式,我們會在利器三中討論。
利器二:佈局結構
每個圖層都是一個矩形,爲了生成佈局結構只能依賴矩形的上下左右座標信息。因此,對佈局結構進行分類時,我們根據矩形與矩形之間的位置關係(相交、相離和包含關係)做了以下分類。
注意:從生成 DSL 的結果來看,包含佈局和成組佈局的處理方式其實是一樣的,都是使用類似於 FrameLayout 的層疊佈局包含內部圖層元素,但是我們仍然保持分類原則(矩形之間的位置關係)不變。
上圖中,相離、包含比較好理解,爲什麼兩個圖層相交的時候,會有成組和懸浮兩種類型的佈局結構呢?我們看下上述成組佈局、懸浮佈局兩個設計稿中分別標出了相交的元素 A、B,它們在位置上的相對關係是一樣的,都是 A、B 兩個圖層對應的矩形框發生了交叉。但是我們希望理想態的 DSL 視圖樹卻有所差異,如下圖所示:
-
成組佈局中:A、B 邏輯上是一個整體,交叉是必然的,最終 DSL 中 A、B 被層疊佈局包含,層疊佈局中沒有其他元素。
-
懸浮佈局中:A、B 邏輯上不是整體,只是碰巧交叉了,最終 DSL 中 A、B 分別在不同的層級中。
因此,對於圖層相交時可能有兩種類型的佈局結構,分別是成組佈局和懸浮佈局。從上圖可以看出使用成組佈局還是懸浮佈局是由圖層內容決定的,那麼就需要算法理解圖層內容了,比如基於 AI 構建樣本庫,記住所有的角標樣式(上面表格中 4 描述的),下次遇到角標相交時就生成成組佈局。
考慮到 AI 模型也是對規則的抽象,我們先搭建一套自定義識別規則。成組佈局其位置信息是有規律可循的,例如:角標經常出現在右上角,標籤經常出現在左上角,頭像經常橫向或者縱向交叉等,因此我們針對圖層之間的位置關係構建了交叉模型,如下圖所示:
上圖的交叉模型可以記住歷史模板中成組佈局圖層之間的位置關係,下次遇到相交佈局時判斷是否在歷史規則庫中即可完成識別,如果在就按成組佈局處理否則按照懸浮佈局處理。下圖是通過歷史模板構建的成組規則庫。
上面介紹了本方案中涉及的 5 種佈局類型,目前來看這五種佈局類型可以描述所有的模板佈局,並且生成代碼符合 RD 的預期。下面展示兩個設計稿 DSL 實例:
利器三:模型評估
在介紹橫豎切割時,可以看到當存在多個切割點時,對所有切割點同時進行了切割,但實際上算法在切割時複雜度會更高,當有三個切割點時,實際上有 5 種切割方式,每種切割方式都會生成一個 DSL。既然有 5 種切割方式,那麼到底應該選擇哪一種 DSL 呢?模型評估算法就是用來解決這個問題的。
目前模型評估算法有兩個指標:佈局節點數和逆佈局指數。
-
DSL 中佈局節點數越少,切割方式越好。
-
逆佈局指數用來評估 DSL 中的行列布局的合理程度,其中逆佈局指數越大越不合理,反之,逆佈局指數越小,切割方式越好。
以下圖爲例,看下視圖不同切割方式下對應的模型評估方式:
如果模型評估算法只衡量佈局節點數的話,那麼會選擇第一種切割方式生成的 DSL 作爲最終的結果。但實際上,第二種切割方式更加合理。在切割方式一中,廣告、立即預約處於一個列布局中,但是橫向對齊方式(交叉軸)卻不一樣,“廣告” 是右對齊,“立即預約” 是左對齊,逆佈局指數表示交叉軸對齊方式不一致的節點數量,因此通過逆佈局指數,我們可以規避掉不合理的切割方式。
2.1.5 列表佈局
上一節介紹了基本的佈局結構,雖然說這些佈局結構已經可以描述所有的 UI 佈局,但是與 RD 的編碼習慣還是有一些差異。
對於上面的佈局,RD 通常不會把相同的 item 寫五遍,而是會將 item 放在一個類似於 ListView 的列表組件中,使代碼看起來簡潔易懂。因此在 DSL 生成階段,除了識別基本的行 / 列 / 包含 / 成組 / 懸浮佈局外,還需要進一步識別行 / 列布局中的元素是否形成列表佈局。
在試驗過程中,我們發現列表佈局分爲兩種:單狀態列表組件和多狀態列表組件。上圖中每一個 item 的佈局結構都是一樣的,我們稱爲單狀態列表組件,再來看一下多狀態列表組件(如下圖所示),每個 item 有多種狀態(選中態和非選中態),並且不同狀態的佈局結構不一致。
對行 / 列布局中單狀態列表組件的識別,只需要比較 item 子視圖樹的結構,子視圖樹結構一致則判斷爲單狀態列表組件。對多狀態列表組件的識別我們採取了自動識別 + 人工干預的方式,自動識別的方式比較粗暴,只要行列布局中子 item 的寬 / 高接近,並且子 item 不是基本組件(基本組件容易形成誤判),就判定爲多狀態列表組件。具體算法是計算子 item 寬高的標準差,小於閾值就判定爲多狀態列表組件,否則不是。公式如下:
那爲什麼還要人工干預呢?因爲是否使用列表組件其實與產品邏輯相關,但是目前我們無法將產品文檔中的邏輯識別出來,只能儘可能識別出所有的多狀態列表組件,並允許用戶對生成結果進行變更。比如上述送戀人的設計稿,產品可能約定每一個 item 都有選中態 / 非選中態兩種狀態,也可能是從業務角度考慮需要着重突出送戀人這個 item,這時每個 item 就只有一種確定的狀態,這兩種不同的產品邏輯在編寫代碼時有不同的最優技術方案。
2.2 視圖樹轉代碼(DSL2Code)
DSL 視圖樹只是生成代碼的中間產物,還需要對 DSL 進行代碼還原,DSL2Code 主要包括兩個步驟:屬性推斷、屬性信息調整。
2.2.1 屬性推斷
屬性推斷包括兩個部分:樣式屬性和結構屬性。樣式屬性包括字體、背景色、圓角等可以直接通過數據源信息中獲取得到的屬性;結構屬性包括大小、內外邊距、主輔軸對齊等結構信息,這些信息無法從數據源中直接獲取,所以結構信息的推斷是這部分工作的重點。
結構信息推斷算法同樣使用遞歸算法作爲主框架,通過一次遞歸對所有元素進行兩次遍歷來完成結構信息的推斷。如下圖所示,在對 DSL 所有節點進行遞歸遍歷時,把所有元素依次加入隊列中,遞歸完成後,再把所有節點依次移出隊列,這樣一進一出便對所有元素完成了兩次遍歷,我們把這兩次遍歷稱爲進隊遍歷和出隊遍歷。
進隊遍歷時,推斷算法根據數據源中信息記錄每個節點的大小和位置信息,並根據位置關係計算每個子節點在父節點中期望的主輔軸對齊方式和內外邊距。出隊遍歷時,父節點會根據子節點期望的對齊方式確定父節點最終的主輔軸對齊方式,並根據子節點的拉伸意圖修正父節點的大小。拉伸意圖即節點的大小不固定,根據顯示內容不同,在水平或垂直方向上可能會變大或變小,例如文本節點根據顯示字數的多少長度會發生變化,字數過多時甚至還會換行。
2.2.2 屬性信息調整
由於輸入源是基於設計稿呈現的靜態效果圖,設計稿中每個元素缺失了真實的業務含義,同樣的展示效果在不同的業務場景中會有不同的屬性要求,對於這部分內容,我們無法從輸入源中進行準確推斷。爲此,我們提供了可視化的屬性信息調整功能來輔助代碼生成,頁面效果如下圖所示,在這個頁面可以對 DSL 中的所有節點屬性進行查看和修改調整。
經過業務信息補充後,便可進行最後的自動代碼轉化,通過語法映射自動把 DSL 轉化成 MTFlexbox 模板代碼。
3 成果展示
下面是設計稿直接生成代碼未經修改展示後的手機屏幕截圖,可以看到取得了不錯的還原效果:
以上就是我們近期對代碼自動生成的探索及實踐,後續我們將引入機器學習及神經網絡算法,對過程進行進一步優化。如果您有其他的看法或建議,也歡迎在文末進行評論,或者跟我們聯繫。
作者簡介
田貝、少寬、騰飛等,美團平臺終端業務研發團隊研發工程師。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/DR2jbqBjUPEH1xR27mArpA