';

这个工具已经完成了:

design-enzyme.com

 


Photoshop CC 2017 终于发布了,一直有些在意 CC 2017 会不会有些革命性的更新,因为之前一直在断断续续的开发一个工具,用来增强 Photoshop 在 UI 设计工作中的“功能”,由于担心 CC 2017 会不会有大的更新和变化,所以开发一直很“谨慎”,而新的 Photoshop 发布了,新的更新并没有太大的变化,而 Adobe XD 也更偏向于交互原型的制作(而且 windows 版还没不出来),对于基本的设计功能并没有太大的增强,所以我新开发的这个工具还是有些意义的。

Photoshop 的不足

前几代 Photoshop 在都对图形设计、和 UI 设计有许多针对性的增强,但是比起 Sketch 来,还是显得有很多不足,尤其 Sketch 各种插件也越来越丰富,而为 Photoshop  开发的的 UI 设计相关插件却很少,Photoshop 好像已经被 UI 设计界抛弃了….

Image title

很期待 Photoshop 什么时候会有像 Sketch 的对象缩放这样程度的更新

 

不过 Photoshop 还是有许多 Sketch 无法替代的地方,比如位图相关功能、和更高的“自由度”,更重要的是 Photoshop 是 Windows 上唯一的选择。 所以我一直在考虑开发一款 Photoshop 的扩展用来增强 Photoshop 在 UI 设计方面的功能。

这个工具的核心理念是“便捷操作”、“控制力”、“自动化”

增强的功能

便捷操作

Photoshop 虽然有个“图形与网页”的工作区预设,但是工具面板本身并没有任何为 “图形与网页” 设计的优化,过去没有比较也就凑合用了,但和 Sketch  比起来,实在是显得操作繁琐。

比如在  UI 设计中修改图层的阴影、描边、填充本来是最常用的功能了,在 Sketch  中选中对象后就可以在属性面板里直接设置了, 但在 Photoshop 中却得打开窗口设置,

Photoshop 中设置一个阴影

 

Sketch 中阴影、填充、描边都直接可以在属性面板中设置

解决这个问题,实际很简单,为 UI 设计做一个单独的属性面板就好了,功能上和 Sketch  差不多,把图层样式的常用参数提升到面板中调节,然而至今没有这样的面板,所以我就来做了:

当然这个扩展不只是做一个类似 Sketch 的属性面板,更重要的是增加对数据的控制力。

控制力

所谓控制力就是对编辑对象各属性的控制能力,能够掌握所有图层的各种属性数据,更随心所欲的操作这些数据,在数据之上赋予逻辑实现各种功能和自动化。

比如在 Photoshop 中是不能把“色相\饱和度”工具用在图层的填充、描边、样式颜色(比如阴影的颜色)中的,而在拥有了操作图层所有属性的能力后,这就成为了可能:

改变图层属性中填充颜色、描边颜色、投影颜色的色相

 

表达式

图层的属性设置可以使用表达式,这个表达式可以是数学公式(这在 Sketch 已经有了 ):

变量系统

Photoshop 有一个变量扩展 Ditto,不过它支持的属性很有限, 而我的新扩展可以在图层几乎所有属性上使用变量(包括图层样式的各种参数),并且可以配合表达式使用,还可以动态的把图层属性值赋值给变量,变量值也可以是一个表达式,除此之外还可以使用预置的动态获取数据的变量(被称为增强子),比如获取把图层宽度设置为父级图层组宽度的一半 – 10: $parent.W /2 - 10

变量可以使用英文也可以使用中文,预置的动态变量也可以用不同语言: ¥父.W  $隣.3.W 

 

 

 

有这样的变量与表达式系统意味着,可以做到数据响应、数据绑定、图层控件化,这么说可能有些抽象,举一些例子:

用模板图层来给影响其他图层

一般用来完成像模板一样作用的在 Photoshop 里有智能对象, Sketch 和 illustrator 里有 symbol,而它们只能做到是简单的复用,而不能更自由的选择只继承模板的一部分,而下面这个例子中白色的实例图层可以只继承模板图层的尺寸属性,而且可以使用表达式进行计算,当改变模板图层的尺寸时,每个实例图层的尺寸都会相应变化:

 

把图层宽度、高度赋值给变量

把图层宽高设置为根据变量

把图层宽高设置为根据变量的一半

不仅仅是尺寸属性可以变量化,任何图层属性都可以使用表达式和变量。

制作设计标准

很多有流程规范的设计总会要制作一个设计规范,而设计规范的制作修改总是繁琐而无趣的工作:要把设计中的元素整理出来,做成一个个图层,而每当要修改设计规范,就是噩梦了。而如果把图层属性用变量连接起来,文档就从“死” 变成“活”的了,修改设计规范中的参数,变化会自动扩散到文档中每个实例:

 

修改色标图层的颜色,会改变每个使用这个变量的图层参数,文本图层内容也可以变成颜色的 HEX 值

 

这样的变量系统在 UI 设计中国际化、主题化、跨平台等等方面能提高的效率有过制作经验的应该都可以想象。

另外掌握了所有图层的属性,这样就可以方面的实现类似 craft 的自动填充功能、生成 Zeplin 那这样的自动标注页面。而 Sketch  、Adobe XD 中很多功能也可以在 Photoshop 中实现,比如 Sketch   的对象缩放:

Image title

能够在 Photoshop 中实现对象缩放,虽然可能不会有 Sketch 那样实时响应的速度

另外还可以想象的是,不仅仅在文档里的数据绑定,还可以把 Photoshop 中图层的各种数据与 CSS 文件、样式 XML 文件里的数据绑定起来,做到在 Photoshop 中编辑图层实时更新在样式中,在 Photoshop 中可视化的编辑样式。

自动化

有了变量系统,每个图层还可以有自定义属性,可以给图层赋予逻辑代码,或给图层赋予“类名”,不同类有不同的逻辑….

能完成的只有想不到了…

比如说可以给图层组增加一个 padding (内边距)属性,影响图层组里图层的位置排列。

 

路线图

UI-DNA 属性面板

这是真正开发的部分,实现的就是上面说的这功能。

 

UI-DNA 功能库

会实现自动填充功能(类似 craft )、生成自动标注页面(类似 Zeplin )之类的功能,

由于开发 UI-DNA 做了很多“基础设施”的代码,这给开发功能提供了很多的便利,所以准备制作一个“扩展的扩展”——胶囊扩展功能,简单来说就是 UI-DNA 的插件。

只要写少量的代码、可以使用 UI-DNA 现成的接口,可以在 UI-DNA 中动态载入不用重启 Photoshop。

 

UI-DNA IDE

技术实现

文档中的变量、属性数据会以文本图层的形式存储在文档中:

 

图层的自定义数据以隐藏文本图层的形式保存在文档中

 

 

面板的界面我是想要尽量与 Photoshop 原生的界面相似,因为估计扩展完成时 Photoshop CC 2017  会发布了,之前制作界面时我预测新的 Photoshop CC 2017 的界面应该会和 Adobe XD 差不多,所以按 Adobe XD 的外观来制作的,最终我失算了  Photoshop CC 2017 界面竟然没有按我预测那样更新,不过这样其实看起来不错我不打算改了。

界面 CSS 库  https://github.com/nullice/Ex-morphogen

 

在技术上实现对图层各种属性的完全控制,是这个扩展开发的一个难点,了解 Photoshop 脚本的应该知道,Photoshop 虽然给 ExtendScript 提供了 DOM 接口,然而这个接口实在是年久失修,功能缺失严重,比如连获取多个选中图层的接口都没有(只能选取一个选中图层),更别提获取图层样式了。这应该就是 Photoshop 上针对 UI 设计相关扩展缺乏很大的原因。像之前所说的扩展 Ditto 就只对 Photoshop 提供了 DOM  接口的图层属性有用。

不过好在 ExtendScript 中有能够使用 Photoshop 底层功能 ActionManger 接口,然而 ActionManger 的接口并不像 DOM 一样有官方文档,可参考资料少得可怜,这让我花了大量时间在摸索 ActionManger  各接口的使用上。

总之最后问题是已经基本解决了,我做了一个把 ActionManger 操作变成传递 json 来操作的库,并封装了许多 DOM 没有的功能,比如获取多选图层、获取图层样式、画板。

如果要写 Photoshop 脚本和扩展的也可以很方便的拿来用(但是还没完全完成,应该有些 BUG 就是了),文末有 Github 地址。

举个例子。像下面这个例子如果单纯用 ActionManger 的话,获取样式得花上 100 行以上代码,设置样式要 200 行以上代码,而使用封装的接口的话:

//使用封装的接口获取当前图层图层样式的投影参数,只要 2 行
var eOb =ki.layer.getLayerEffectsObject(Kinase.REF_ActiveLayer, null)
ki.layer.getEffectsList_universal (eOb, "dropShadow", true)

//轻松获取和设置图层样式(支持多重样式)===>
[
  {
    "enabled": true,
    "present": true,
    "showInDialog": true,
    "mode": "normal",
    "color": {
      "red": 6.00000011734664,
      "grain": 152.486380040646,
      "blue": 255
    },
    "opacity": 70,
    "useGlobalAngle": false,
    "localLightingAngle": 90,
    "distance": 7,
    "chokeMatte": 1,
    "blur": 10,
    "noise": 0,
    "antiAlias": false,
    "transferSpec": {
      "name": "线性"
    },
    "layerConceals": true
  }
]
// 使用正常人是难以看懂的 ActionManger 的话:
var idsetd = charIDToTypeID( "setd" );
    var desc2896 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref551 = new ActionReference();
        var idPrpr = charIDToTypeID( "Prpr" );
        var idLefx = charIDToTypeID( "Lefx" );
        ref551.putProperty( idPrpr, idLefx );
        var idLyr = charIDToTypeID( "Lyr " );
        var idOrdn = charIDToTypeID( "Ordn" );
        var idTrgt = charIDToTypeID( "Trgt" );
        ref551.putEnumerated( idLyr, idOrdn, idTrgt );
    desc2896.putReference( idnull, ref551 );
    var idT = charIDToTypeID( "T   " );
        var desc2897 = new ActionDescriptor();
        var idScl = charIDToTypeID( "Scl " );
        var idPrc = charIDToTypeID( "#Prc" );
        desc2897.putUnitDouble( idScl, idPrc, 100.000000 );
        var idDrSh = charIDToTypeID( "DrSh" );
            var desc2898 = new ActionDescriptor();
            var idenab = charIDToTypeID( "enab" );
            desc2898.putBoolean( idenab, true );
            var idpresent = stringIDToTypeID( "present" );
            desc2898.putBoolean( idpresent, true );
            var idshowInDialog = stringIDToTypeID( "showInDialog" );
            desc2898.putBoolean( idshowInDialog, true );
            var idMd = charIDToTypeID( "Md  " );
            var idBlnM = charIDToTypeID( "BlnM" );
            var idNrml = charIDToTypeID( "Nrml" );
            desc2898.putEnumerated( idMd, idBlnM, idNrml );
            var idClr = charIDToTypeID( "Clr " );
                var desc2899 = new ActionDescriptor();
                var idRd = charIDToTypeID( "Rd  " );
                desc2899.putDouble( idRd, 95.998535 );
                var idGrn = charIDToTypeID( "Grn " );
                desc2899.putDouble( idGrn, 233.000336 );
                var idBl = charIDToTypeID( "Bl  " );
                desc2899.putDouble( idBl, 179.997253 );
            var idRGBC = charIDToTypeID( "RGBC" );
            desc2898.putObject( idClr, idRGBC, desc2899 );
            var idOpct = charIDToTypeID( "Opct" );
            var idPrc = charIDToTypeID( "#Prc" );
            desc2898.putUnitDouble( idOpct, idPrc, 60.000000 );
            var iduglg = charIDToTypeID( "uglg" );
            desc2898.putBoolean( iduglg, false );
            var idlagl = charIDToTypeID( "lagl" );
            var idAng = charIDToTypeID( "#Ang" );
            desc2898.putUnitDouble( idlagl, idAng, 90.000000 );
            var idDstn = charIDToTypeID( "Dstn" );
            var idPxl = charIDToTypeID( "#Pxl" );
            desc2898.putUnitDouble( idDstn, idPxl, 3.000000 );
            var idCkmt = charIDToTypeID( "Ckmt" );
            var idPxl = charIDToTypeID( "#Pxl" );
            desc2898.putUnitDouble( idCkmt, idPxl, 0.000000 );
            var idblur = charIDToTypeID( "blur" );
            var idPxl = charIDToTypeID( "#Pxl" );
            desc2898.putUnitDouble( idblur, idPxl, 6.000000 );
            var idNose = charIDToTypeID( "Nose" );
            var idPrc = charIDToTypeID( "#Prc" );
            desc2898.putUnitDouble( idNose, idPrc, 0.000000 );
            var idAntA = charIDToTypeID( "AntA" );
            desc2898.putBoolean( idAntA, false );
            var idTrnS = charIDToTypeID( "TrnS" );
                var desc2900 = new ActionDescriptor();
                var idNm = charIDToTypeID( "Nm  " );
                desc2900.putString( idNm, """线性""" );
            var idShpC = charIDToTypeID( "ShpC" );
            desc2898.putObject( idTrnS, idShpC, desc2900 );
            var idlayerConceals = stringIDToTypeID( "layerConceals" );
            desc2898.putBoolean( idlayerConceals, true );
        var idDrSh = charIDToTypeID( "DrSh" );
        desc2897.putObject( idDrSh, idDrSh, desc2898 );
    var idLefx = charIDToTypeID( "Lefx" );
    desc2896.putObject( idT, idLefx, desc2897 );
executeAction( idsetd, desc2896, DialogModes.NO );

最后

目前的情况是,这个扩展已经基本解决了在技术上的难题,只要有足够的工作量就能完成了,目前也已经有一个可运行部分功能的雏形了。敬请期待测试版发布吧,同时欢迎提出意见。

最后,这是工具是开源、免费的。

目前定名为:设计构建工具 UI-DNA

源代码:github.com/nullice/UI-DNA