如何構建一個理想UI程式碼表達的自動化工具?
作者:閒魚技術-吉豐
基於設計師產出的 Sketch,甚至是一張 PNG,就能自動生成高可維護可擴充套件的 UI 程式碼,質量堪比一位資深前端工程師, 一定是一件讓整個大前端領域都為之尖叫的事情。
出於這樣一個讓人興奮的命題,閒魚團隊打造了 ui-automation 工具 。
背景
如何讓前端,客戶端的 UI 開發更有效率,一直是一個大前端領域熱門話題。
從純手寫 UI 程式碼,到編寫 XML 表達 UI,到所見即所得的 UI 編輯器。每一步都在極大提升著大前端領域的生產效率。
當下,隨著計算機視覺技術,深度學習技術在工程側的大量使用,閒魚團隊的同學們認為,基於當前的技術,是完全能夠完成接近甚至超越資深前端工程師編寫的理想的 UI 表達。
問題
類比基於傳統的掃描演算法和最新的 pix2code 的深度學習技術。它們確實在有些場景下,生成了渲染完全一致的 UI 程式碼,但是往往可維護性可擴充套件性差,除了在簡單的靜態頁面中能有所應用,在大部分需要動態能力的場景,無能為力。
核心的生成的 UI 程式碼質量問題,是之前的這些工具無法跨越的鴻溝。
而閒魚的 ui-automation 最核心的是要解決程式碼質量問題,使得生成的 UI 表達是最理想的,真正解放開發同學。
ui-automation 流程
- 資訊提取 => 2. DSL 推導 => 3. 目標平臺程式碼
相比於渲染流程
- ui 程式碼 => 2. gpu 渲染 => 3. 畫面
是一個逆向的流程。
資訊提取層
-
基於 Skecth 資訊的提取和預處理
資訊全,精確,但是有冗餘,干擾資訊。
-
基於圖片資訊的提取
資訊乾淨,沒有冗餘資訊。
DSL 層
將扁平化的上游資訊,樹形化,同時補充了完整的佈局約束的資訊。
模版層
根據上游的 DSL 資訊, 生成不同平臺的目的碼,如 flutter,weex 等。
本文重點闡述中間層 DSL 的定義和推導過程
基礎 UI 元素
我們定義了 3 中最基礎的 UI 元素
Shape,Text, Image。
結構上一個基礎 UI 元素有一下幾部分構成:
- 內容
- 渲染樣式
-
佈局樣式
佈局樣式沿用了經典 flexbox 的模型。
DSL 層的輸入
輸入中包含了上述 3 類基本的 UI 元素, 包含它們的內容,它們的渲染樣式,以及相對螢幕的絕對座標和大小,其中層級結構和佈局屬性在演算法的推導中給出。
DSL 層的輸出
輸出是一個類似 dom 樹的結構,有完整的佈局屬性,除了上述三類基礎元素外,還有基礎容器元件,CI 元件,BI 元件。
DSL 分層處理
在 DSL 的推導過程中, 分兩大層
分組層
關注於巨集觀資訊的處理。目標是完成一棵最佳的 ViewTree,以及掃描出足夠的輔助資訊給下一層屬性推導使用。
(1)二元切分
對一組元素進行劃分的時候
任何元素之間可能存在如下兩種關係 1. 父子關係 2. 兄弟關係
根據兩種關係的特點,我們使用了兩個不同的模型來對陣列切割(一分為二)。 1. 父子關係使用重合模型來劃分。
重合模型會突出明顯的若干個 background|foreground 圖層 在 x,y 兩個方向上都重合了剩下的所有元素。 2. 兄弟關係使用投影模型來劃分。
投影模型,通過往一個方向上投影,兄弟關係的元素間 會存在明顯的獨立且連續分佈的規律。兩個方向都可投影的情況下, 優先水平方向。
在對重合模型和投影模型做適度優化後,第一次分組的容錯性,穩定性,準確性得到了極大的提升。
一個簡單的遞迴虛擬碼
Group(...children:View[]){}
type slice = ( views:View[] ) => View[][] //sliceByOverlaps(views) || sliceByProjection(views)
const regroup = ( views:View[] ) => views.length === 1 ? views[0] : new Group(...slice(views).map(regroup))
經過上步驟切分後, 得到的是一棵標準意義上的二叉樹。
例一:
注:7 號元素是簡化的處理,實際包含了 3 個基礎元素。
輸入:
[3, 2, 1, 4, 5, 6, 7]
輸出:
[
3,
[
[2, 1],
[
4,
[
[5, 6],
7
]
]
]
]
這樣的一棵二叉樹。
(2) 歸併
標準意義上的二叉樹, 並不符合我們的需求,所以需要做一次扁平化的歸併,將同方向的父子節點歸併為一個陣列, 降低樹的深度。
一個簡單的遞迴虛擬碼
const flattenOnDirection = (view:View, parentDir: FlexDirection) => {
return view.isContainer ?
? view.dir != null && view.dir == parentDir
? flatten(view.children.map(child => flattenOnDirection(child, view.dir)))
: [flattenGroup(view)]
: [view]
}
const flattenGroup = (view: View) => {
if(view.isContainer) {
view.children = flatten(node.children.map(l => flattenOnDirection(l, dir)), true)
}
return view
}
經過這層處理上例一的樹修正為
[
3,
[
[2, 1],
4,
[5, 6],
7
]
]
(3) 排序層
根據容器方向,做一輪左到右,上至下的排序。
(4) 感知輔助線
對 ViewTree 做一次深度遍歷, 掃描出輔助線的資訊,將影響後續的對齊方式的推導,但並不影響 ViewTree 結構。
如上圖, 灰色框表示基礎元素,紅色框比較容器,黃色虛線表示掃描出輔助線的資訊。因為有輔助線資訊的存在,我們才能讓第 3 行的文字,左對齊,而非右對齊。人的視覺資訊處理亦是如此。
(5) 疏密切分
根據疏密分佈, 在對同一容器下的孩子節點,根據疏密分佈切分。
如上圖,同在水平方向的兄弟節點,根據疏密關係,分解為左族群和右族群,一個向左對齊,一個向右對齊,中間的剩餘空間是共享的。
(6) 掃描網格分佈資訊
這裡用到圖形相似度的演算法,若干水平行, 每行的元素子樹之間相似。
在垂直方向掃描得到最大不重複的組合, 打破原有層級約束重新組合。
如:
掃描後, 打破原有層級約束後, 重新組合
(7) 感知中間線
對 ViewTree 做一次深度遍歷, 掃描出有效的中間線資訊,會影響 ViewTree 結構。
(8) 合併層 1. 合併背景圖層到容器的背景屬性 2. 合併背景圖層到 Text 的背景屬性 3. 合併僅包含一個孩子節點的容器
大致經過上述 8 個小層的處理後, 我們得到了一個理想的 ViewTree。下一步開始我們的屬性推導。
屬性推導層
關注於區域性資訊的深度推演。
(1) 推導每一個容器的方向
推導方向是最獨立的,僅僅依賴於孩子節點的分佈情況。
(2) 推導每一個節點是 在流裡面的,還是脫離流絕對的
這裡依賴一個重合衝突演算法。大體是重合衝突率高的,就是絕對的元素,重合衝突率低的是流式元素。同時存在一定的冗餘能力,允許小部分的重疊(負 margin),這樣極大的提高了線性佈局的動態性。
(3) 推導每一個節點的大小。
以一個盡力撐滿的貪心模型,推匯出每一個元素的大小。同時盡力用屬性約束取代直接給定寬或定高的形式,來達元素大小是到跟隨內容或跟隨孩子節點或跟隨父容器的動態性。
對於一個容器的副軸的大小的處理,會略微複雜些,
(4) 推匯出一些特殊佈局
- 網格
-
左右對齊佈局
等
(5) 推導主軸方向對齊方式
優先居中, 其次居左, 最後居右。
(6) 推導副軸方向對齊方式
(7) 推導位置
- 流式元素 通過 margin 表示座標。 居中通過(5)(6)推導的 JustifyContent,AlignItems,AlignSelf 等要素描述。
- 絕對元素 通過 left, top, bottom, right 等描述座標。居中通過 transform 描述。
(8) 推導 padding
ui-automation 工具目前已經運用在閒魚內部的各個業務場景之中,伴隨著大量的應用,工具本身同樣日益進化。
最後,閒魚技術團隊廣招各類方向的達人,無論你是精通移動端,前端,後臺,還是機器學習,音視訊,自動化測試等,都歡迎投遞簡歷加入我們,一同用技術改善生活!
相關文章
- 教你如何搭建一個自動化構建的部落格
- Element-UI 中 Make 自動化構建分析UI
- Jenkins + Gitee 實現程式碼自動化構建JenkinsGitee
- 自動化測試的理想境界:AppCrawler自動遍歷工具APP
- python+robotframework --第一個UI自動化指令碼PythonFrameworkUI指令碼
- 如何構建自動化的前端開發流程前端
- 一個 UI 自動化問題諮詢UI
- 自動化構建映象:Packer
- Jenkins和maven自動化構建java程式JenkinsMavenJava
- Maven:自動化構建工具Maven
- 自動化構建工具 Grunt
- maven自動化構建工具Maven
- 總結下 ui 自動化驅動架構UI架構
- 使用vue構建一個自動建站專案Vue
- [轉]:如何快速構建一個簡單的程式
- 如何快速從 0 到 1 構建起一個公司的自動化介面
- 構建高效的自動化測試框架框架
- 低程式碼如何推動自動化未來
- 打造自己的php半自動化程式碼審計工具PHP
- 用 python 寫一個自動化部署工具Python
- 【Flutter】如何寫一個Flutter自動打包成iOS程式碼模組的指令碼FlutteriOS指令碼
- 程式碼來構建一個簡單的compilerCompile
- Webpack自動化構建實踐指南Web
- 淺談自動化構建之grunt
- 淺談自動化構建之gulp
- Grunt自動化構建環境搭建
- App案例實踐:一款好用的UI自動化測試工具——KatalonAPPUI
- Linux構建Git程式碼倉庫與自動部署LinuxGit
- 介面自動化與ui自動化區別UI
- 擼了一個Golang的包管理/自動化構建工具,歡迎大家拍磚Golang
- 自動化單元工具EvoSuie的程式碼覆蓋報告UI
- 一個可以自動構建CURD控制器的go-apiGoAPI
- Jenkins自動化前端專案構建Jenkins前端
- Android Jenkins自動化構建之路AndroidJenkins
- 自動化測試系列 —— UI自動化測試UI
- 程式碼視覺化的自動化之路視覺化
- 利用fastlane進行專案的自動化構建AST
- 如何構建一個系統?