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技术有所推动。但愿能让更多人跟咱们一块儿找寻更有效率的写代码方法,若是有任何想法欢迎与咱们交流,咱们也持续不断地在进化工具中,谢谢各位的阅读!

做者:闲鱼技术-上叶,余晏

原文连接

​本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索