如何构建一个理想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 工具目前已经运用在闲鱼内部的各个业务场景之中,伴随着大量的应用,工具本身同样日益进化。
最后,闲鱼技术团队广招各类方向的达人,无论你是精通移动端,前端,后台,还是机器学习,音视频,自动化测试等,都欢迎投递简历加入我们,一同用技术改善生活!
如何构建一个理想UI代码表达的自动化工具?相关推荐
- python自动化工具_AWD_Hunter, 一个基于Python2.7的AWD自动化工具
AWD_Hunter, 一个基于Python2.7的AWD自动化工具 免得比赛时手忙脚乱,时间有限,后续或加入自动submit flag什么的 安装依赖库 sudo python -m pip ins ...
- python 网页自动处理_推荐一款 10 行 Python 代码实现网页自动化工具
各种各样的网站在我们日常工作和学习中占据着举足轻重的地位,学习.影音娱乐.查询资料.协同办公,越来越多的任务都被迁移到浏览器 因此,网页也蕴含着很多有价值.我们能够用得到的资源 例如,数据.歌曲.影视 ...
- 代码实现ps切换工具
在下最近在研究制作ps的扩展插件,由于工作原因,设计使用ps较多,但是手动处理倒角还是比较麻烦,所以想开发了一款倒角用的小扩展插件,减少一下工作量,到网上找了些有关ps插件制作的教程,先实现一下ps的 ...
- C/C++代码缺陷静态检查工具cppcheck
cppcheck介绍和安装 CppCheck是一个C/C++代码缺陷静态检查工具.静态代码检查是检查代码是否安全和健壮,是否有隐藏问题. CppCheck只检查编译器检查不出来的bug,不检查语法错误 ...
- 如何用 Slack 和 Kubernetes 构建一个聊天机器人?| 附代码
作者 | Alexander Kainz 译者 | 天道酬勤,责编 | Carol 出品 | AI科技大本营(ID:rgznai100) ChatOps可以让你使用基于聊天的接口来管理DevOps任务 ...
- easyui treegrid 获取新添加行inserted_18行JavaScript代码构建一个倒数计时器
有时候,你会需要构建一个JavaScript倒计时时钟.你可能会有一个活动.一个销售.一个促销或一个游戏.你可以用原生的JavaScript构建一个时钟,而不是去找一个插件.尽管有很多很棒的时钟插件, ...
- 转载:谢谢原作者:块设备驱动实战基础篇一 (170行代码构建一个逻辑块设备驱动)
1 内核块设备驱动基础学习与实战 1.1 设备驱动IO架构初探 操作系统是如何将数据读到缓冲区的,发生了什么?我们带着这样的问题,粗略走一下read调用系统过程,希望这个初探,可以唤起大家研究操作 ...
- 用500行纯前端代码在浏览器中构建一个Tableau
2019独角兽企业重金招聘Python工程师标准>>> 在Gartner最新的对商务智能软件的专业分析报告中,Tableau持续领跑.Microsoft因为PowerBI表现出色也处 ...
- 几十行python代码构建一个前后端分离的目标检测演示网站,代码开源
在深度学习更讲究实用和落地的今天,构建一个简单的,可以利用浏览器和后端交互的演示性 Demo 可以说非常重要且实用了.本文我们将简单的介绍如何用几十行核心代码构建一个好用的.前后端分离的Demo. 2 ...
最新文章
- linux学习5-shell编程
- C++对象模型2——编译器生成构造函数的几种情况
- 三大牛人看外国文献的方法
- element 密码输入框用*显示_用 Java 实现天天酷跑,这个真的有点强了
- javaweb学习总结(十九):JSP标签
- C或C 如何通过程序执行shell命令并获取命令执行结果?
- 给你这张图,你能搜索到来历吗
- excel if函数 android,Excel函数公式:多条件判断你还在用If函数来实现,那就真的Out了...
- 梯度投影算法 matlab,梯度投影法及其Matlab实现
- python决策树代码解读_建模分析之机器学习算法(附pythonR代码)
- 力扣--48旋转图像(中等)
- 聚类算法实践——PCCA、SOM、Affinity Propagation
- Linux 设备驱动 ==== 字符驱动
- wsimport简介
- RAID磁盘阵列相关技术详解
- android第三方好用,有哪些好用的安卓ROM值得推荐?安卓端最好的第三方ROM介绍...
- 番茄炒鸡蛋里隐藏的九大秘密功效
- 实时编译、动态执行C/C++源码函数
- OpenCV 画任意圆弧曲线
- 《Team Geek: A Software Developer's Guide to Working Well with Others》读书笔记(三)
热门文章
- sqlalchemy.exc.InternalError: (pymysql.err.InternalError) Packet sequence number wrong - got 40 expe
- 【ES6】数组的拓展
- GTX 1080Ti + cuda8.0 + cuDNN6.0 安装及测试
- 语义分割--Attention to Scale: Scale-aware Semantic Image Segmentation
- 目标定位--Deep Self-Taught Learning for Weakly Supervised Object Localization
- Intent和PendingIntent的区别
- 在CLion中运行Ninja项目
- java四种xml_Java中四种XML解析技术
- shell脚本中的变量
- Java 批量文件不打包下载_【Java】Java批量文件打包下载zip