这一部分主要是库相关的高级内容,每个主题不会详细介绍一些初级的内容,如果读者对某个主题感兴趣并且有一些疑问可以咨询作者。
自托管远端库
在 Sketch 51 及以后版本可以通过 sketch
协议打开网络上特定格式的 XML/RSS 文件 来订阅库。XML/RSS 文件内记录 Sketch 文档的下载地址、更新时间、版本等信息,文档下载完成之后将自动加入库列表中。一个 XML 文档对应一个 Sketch 文档,这些内容可以是动态的,这样服务器端可以加入用户验证、订阅购买等功能,也很容易被整合到其他一些在线服务中。
公司内部往往并不需要这样复杂的功能,只需有服务器可以托管一个简单的静态服务就行,一些可以访问原始文件路径的网盘或类似 GitHub/GitLab 的代码托管系统也是可以的,总之将一对一的 XML 和 Sketch 文件放到网络上,并且可以用固定地址访问到原始文件。
XML 格式如下,当更新 Sketch 文档时同时需要更改版本号。发布时间与文件长度在当前测试的版本中并没有发现有实际作用,可以忽略。
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>...</title>
<description>...</description>
<link>...</link>
<image>
<url>...</url>
<title>...</title>
<link>...</link>
</image>
<generator>Sketch</generator>
<lastBuildDate>[UTC Time]</lastBuildDate>
<language>en</language>
<item>
<title>...</title>
<pubDate>[UTC Time]</pubDate>
<enclosure url="..." sparkle:version="[version]" length="..." type="application/octet-stream" />
</item>
</channel>
</rss>
<link>
标签内容为项目主页的 URL 地址,用于在库列表内的库右键菜单显示 “View in Browser” 项。
<image> <url>
标签内容为在下载过程中显示图片的 URL 地址,图片尺寸为 200x160px。
<item> <title>
标签内容为库显示在列表上的名称。
<lastBuildDate>
和 <pubDate>
标签内容为 UTC Time 字符串,但库列表中显示的为文件属性上的修改时间。
<enclosure url>
标签属性内容为 Sketch 文件的 URL 地址,如果发布在公网上,XML 和 Sketch 文件的 URL 地址都必须是 HTTPS 协议的,否则无法载入库,内网的文件并没有这个限制。GitHub、GitLab 和 Dropbox 等平台都能提供 HTTPS 的文件地址。
将 XML 和对应的 Sketch 文档都传到网上之后,需要给一个入口,可以在网页或邮箱内容上添加一个链接指向 XML 文件,HTML 代码格式如下,注意 URL 参数的地址需要转码。
<a href="sketch://add-library?url=https%3A%2F%2Fexample.com%2Fui_kit.xml">Add to Library</a>
URL 转码可以通过在浏览器的 Console 输入类似以下的代码获得。
encodeURIComponent("https://example.com/ui_kit.xml")
地址 sketch://add-library?url=https%3A%2F%2Fexample.com%2Fui_kit.xml
也可以在 Finder 的 “链接服务器” 上打开。
当 Sketch 检测到更新时会以系统通知形式通知用户,库面板中的相应文件会出现下载按钮。
使用插件同步库
使用插件同步库的做法是将 Sketch 文件保存在 Sketch 插件内,安装插件时自动将文件载入到库面板中,之后通过插件的更新功能来提示设计师更新组件。
Sketch 插件其实是一个带有 “.sketchplugin” 后缀的特定结构的文件夹,将所有的 Sketch 文件放到插件内的 “Contents/Resources” 文件夹下,整个插件的目录结构如下。
./Library_Sync_Example.sketchplugin
└── Contents
├── Resources
│ ├── icon.sketch
│ └── ui.sketch
└── Sketch
├── library.js
└── manifest.json
编辑 “manifest.json”,这里配置让 Sketch 监视的一些动作,实现当插件安装或被重新启用时,和创建新文档时载入库,在插件卸载或禁用时删除库。
{
"name": "Library Sync Example",
"description": "...",
"author": "...",
"email": "...",
"homepage": "...",
"appcast": "https://.../appcast.xml",
"version": "1.0",
"identifier": "com.sketch.library.sync.example",
"icon": "icon.png",
"commands": [
{
"handlers": {
"actions": {
"Startup": "addLibrary",
"Shutdown": "addLibrary",
"OpenDocument": "addLibrary"
}
},
"script": "library.js"
}
]
}
编辑 “library.js”,使用 Sketch JavaScript API 几行代码就可以实现载入和删除库。
var addLibrary = function(context) {
var Library = require("sketch/dom").Library;
var libraryFiles = [
"icon.sketch",
"ui.sketch"
];
libraryFiles.forEach(function(fileName) {
var libraryUrl = context.plugin.urlForResourceNamed(fileName);
if (libraryUrl) {
var libraryPath = String(libraryUrl.path());
var library = Library.getLibraryForDocumentAtPath(libraryPath);
AppController.sharedInstance().checkForAssetLibraryUpdates();
if (context.action == 'Shutdown') {
library.remove();
}
}
});
};
这样便完成整个插件的主要内容,插件没有菜单项,需要通过插件管理面板来卸载,插件一安装库就被自动载入。插件的更新需要在插件的 “manifest.json” 设置 “appcast” 的地址,只要安装插件的电脑可以访问这个地址,检测到版本信息不同时就会启动后台下载。
appcast.xml 格式如下,格式与上文的远端库 XML 相似,需要保存插件新版的 ZIP 格式压缩包,版本号信息必须与压缩包的新版插件信息一致。每次更新除了 Sketch 文档外,还需要修改 “appcast.xml” 和 “manifest.json” 这两个文件上版本号信息。
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>Library Sync Example</title>
<link>http://.../appcast.xml</link>
<description>...</description>
<language>en</language>
<item>
<title>1.1</title>
<enclosure
url="http://...zip"
sparkle:version="1.1"
type="application/octet-stream"/>
</item>
</channel>
</rss>
建议把整个项目托管在类似 GitHub/GitLab 之类的程序上,程序会给整个项目一个类似 http://.../user/project/archive/master.zip
的压缩包格式地址,或者利用 Tags 功能将某次提交作为发布版本,地址类似 http://.../user/project/archive/tagname.zip
,这样就不需要人工打包插件。
使用 Sketch 脚本输出资源
可以在 Sketch 的 “Run Script…” 弹出界面上,直接编写脚本并运行,这种形式通常用于运行某些简短的代码来解决某些特定的问题,或者测试代码片段,或者输出某些信息,这些代码也可以被保存成插件。
现在需要为一个图标库导出多种尺寸 PNG,如果图标都是 Symbol Master,但是文档中还有一些例如色彩的 Symbol 或者外部 Symbol 不希望导出,需要对文档中的 Symbol 进行一些过滤。
var sketch = require("sketch/dom");
var document = sketch.getSelectedDocument();
// 文档中所有组件
var symbols = document.getSymbols();
// 输出组件名
symbols.forEach(function(symbol) {
console.log(symbol.name);
});
上面的代码会列出所有 Symbol,可以根据实际情况过滤某种名称的组件。
// 过滤文档中所有组件
var symbols = document.getSymbols().filter(function(symbol) {
return !/^\*/.test(symbol.name);
});
或者只处理当前页面的组件。
// 过滤文档中所有组件
var symbols = document.getSymbols().filter(function(symbol) {
return symbol.sketchObject.parentGroup() == context.document.currentPage();
});
或者只处理选中的组件。
// 过滤文档中所有组件
var symbols = document.selectedLayers.layers.filter(function(layer) {
return layer.type == "SymbolMaster";
});
导出资源,如果 options 没有 output 设置,默认会将文件保存到 “~/Documents/Sketch Exports” 目录下。
// 导出资源
symbols.forEach(function(symbol) {
var options = {
scales: "1, 1.5, 2, 3, 4",
formats: "png",
output: "~/Desktop/Sketch_Exports"
};
sketch.export(symbol, options);
});
实际情况下,通常会有需要询问保存路径。
// 询问保存路径
var panel = NSOpenPanel.openPanel();
panel.setCanChooseDirectories(true);
panel.setCanChooseFiles(false);
panel.setCanCreateDirectories(true);
if (panel.runModal() == NSOKButton) {
var savePath = panel.URL().path();
// 导出资源
symbols.forEach(function(symbol) {
var options = {
scales: "1, 1.5, 2, 3, 4",
formats: "png",
output: String(savePath)
};
sketch.export(symbol, options);
console.log(`${options.output}/${symbol.name}.${options.formats}`);
});
}
新的 Sketch JavaScript API 的导出目前只能将资源导出到指定的目录,实际情况下通常需要修改资源保存路径,另外资源缩放会增加类似 “@2x” 的后缀。目前在实际使用最常用的还是使用 document.saveExportRequest_toFile
和 document.saveArtboardOrSlice_toFile
来导出资源,这两个方法可以修改文件路径不受图层名限制。
document.saveExportRequest_toFile(exportRequest, filePath);
document.saveArtboardOrSlice_toFile(artboardOrSlice, filePath);
导出 Android 多尺寸 PNG
假设画板的名称格式类似 “icon/action/done”,我们需要导出 Android 平台的多分辨率 PNG 资源,另外还要在文件名前多增加一个表示不同分辨率的文件夹,例如 “icon/action/drawable-xhpi/done” 和 “icon/action/drawable-xxhpi/done”。
var sketch = require("sketch/dom");
var document = sketch.getSelectedDocument();
// 处理选中的组件
var symbols = document.selectedLayers.layers.filter(function(layer) {
return layer.type == "SymbolMaster";
});
// 询问保存路径
var panel = NSOpenPanel.openPanel();
panel.setCanChooseDirectories(true);
panel.setCanChooseFiles(false);
panel.setCanCreateDirectories(true);
if (panel.runModal() != NSOKButton) {
return;
}
var savePath = panel.URL().path();
// 遍历要导出的组件
symbols.forEach(function(symbol) {
// 创建 Export Request
var ancestry = symbol.sketchObject.ancestry();
var exportRequest = MSExportRequest.exportRequestsFromLayerAncestry(ancestry).firstObject();
// 设置格式为 PNG
exportRequest.setFormat("png");
// Android 文件名称与缩放对应关系
var dpis = {
mdpi: 1,
hdpi: 1.5,
xhdpi: 2,
xxhdpi: 3,
xxxhdpi: 4
};
for (var dpi in dpis) {
// 在文件名前加上分辨率文件夹
var name = symbol.name.split("/");
name.splice(-1, 0, "drawable-" + dpi);
name = name.join("/");
// 设置缩放
exportRequest.setScale(dpis[dpi]);
// 导出资源
context.document.saveExportRequest_toFile(exportRequest, `${savePath}/${name}.png`);
}
});
导出 iOS 多尺寸 PNG
假设画板的名称格式类似 “icon/action/done”,iOS 资源保存路径为 “icon/action/ios/done.png”、 “icon/action/ios/done@2x.png” 和 “icon/action/ios/done@3x.png”。
// 遍历要导出的组件
symbols.forEach(function(symbol) {
// 创建 Export Request
var ancestry = symbol.sketchObject.ancestry();
var exportRequest = MSExportRequest.exportRequestsFromLayerAncestry(ancestry).firstObject();
// 设置格式为 PNG
exportRequest.setFormat("png");
// iOS 缩放和文件后缀对应关系
var scales = [
{ scale: 1, suffix: "" },
{ scale: 2, suffix: "@2x" },
{ scale: 3, suffix: "@3x" }
];
scales.forEach(function(item) {
// 在文件名前加上 ios 文件夹
var name = symbol.name.split("/");
name.splice(-1, 0, "ios");
name = name.join("/");
// 设置缩放
exportRequest.setScale(item.scale);
// 导出资源
context.document.saveExportRequest_toFile(exportRequest, `${savePath}/${name}${item.suffix}.png`);
}
});
导出 PDF
PDF 格式用于 iOS 和 macOS 开发,假设画板的名称格式类似 “icon/action/done”,PDF 资源保存路径为 “icon/action/pdf/done.pdf”。
// 遍历要导出的组件
symbols.forEach(function(symbol) {
// 在文件名前加上 PDF 文件夹
var name = symbol.name.substring(0, symbol.name.lastIndexOf("/")) + "/pdf" + symbol.name.substring(symbol.name.lastIndexOf("/"));
// 导出资源
context.document.saveArtboardOrSlice_toFile(symbol.sketchObject, `${savePath}/${name}.pdf`);
});
导出 SVG
PDF 格式用于网页平台或用于转换为其他类似字体等格式,假设画板的名称格式类似 “icon/action/done”,SVG 资源保存路径为 “icon/action/svg/done.svg”。
// 遍历要导出的组件
symbols.forEach(function(symbol) {
// 在文件名前加上 SVG 文件夹
var name = symbol.name.substring(0, symbol.name.lastIndexOf("/")) + "/svg" + symbol.name.substring(symbol.name.lastIndexOf("/"));
// 导出资源
context.document.saveArtboardOrSlice_toFile(symbol.sketchObject, `${savePath}/${name}.svg`);
});
解决组件与资源的名称差异
组件为了方便检索会将其分类,例如一套图标有多种风格,组件名称可能按照类似下面的 “风格/分类/名称/尺寸” 格式命名。
Icon / Rounded / Action / Done / 16
Icon / Rounded / Action / Done / 24
Icon / Rounded / Action / Done All / 16
Icon / Rounded / Action / Done All / 24
Icon / Outlined / Action / Done / 16
Icon / Outlined / Action / Done / 24
Icon / TwoTone / Action / Done / 16
Icon / TwoTone / Action / Done / 24
而资源却希望保存成类似下面的格式,还有多种尺寸资源。人工增加切片,并修改图层名的方式工作量非常大。
round/action/drawable-xhdpi/ic_done_16dp.png
round/action/drawable-xhdpi/ic_done_24dp.png
round/action/drawable-xhdpi/ic_done_all_16dp.png
round/action/drawable-xhdpi/ic_done_all_24dp.png
outlined/action/drawable-xhdpi/ic_done_16dp.png
outlined/action/drawable-xhdpi/ic_done_24dp.png
twotone/action/drawable-xhdpi/ic_done_16dp.png
twotone/action/drawable-xhdpi/ic_done_24dp.png
只要组件名称与最终资源名称有一定规律的对应关系,脚本可以在导出前修改资源名称,而保存文件的组件名称不变,也不增加额外的切片图层。
// 遍历要导出的组件
symbols.forEach(function(symbol) {
// 创建 Export Request
var ancestry = symbol.sketchObject.ancestry();
var exportRequest = MSExportRequest.exportRequestsFromLayerAncestry(ancestry).firstObject();
// 设置格式为 PNG
exportRequest.setFormat("png");
// Android 文件名称与缩放对应关系
var dpis = {
mdpi: 1,
hdpi: 1.5,
xhdpi: 2,
xxhdpi: 3,
xxxhdpi: 4
};
for (var dpi in dpis) {
// 重新组合名称,并在文件名前加上分辨率文件夹
var p1, p2, p3, p4, p5;
[p1, p2, p3, p4, p5] = symbol.name.split(/\s*\/\s*/);
var name = `${p2}/${p3}/drawable-${dpi}/ic_${p4}_${p5}dp`.replace(/\s+/g, "_").toLowerCase();
// 设置缩放
exportRequest.setScale(dpis[dpi]);
// 导出资源
context.document.saveExportRequest_toFile(exportRequest, `${savePath}/${name}.png`);
}
});
如果是导出类似以下的 iOS 平台格式。
round/action/ios/ic_done_16pt.png
round/action/ios/ic_done_16pt@2x.png
round/action/ios/ic_done_16pt@3x.png
代码修改如下。
// iOS 缩放和文件后缀对应关系
var scales = [
{ scale: 1, suffix: "" },
{ scale: 2, suffix: "@2x" },
{ scale: 3, suffix: "@3x" }
];
scales.forEach(function(item) {
var p1, p2, p3, p4, p5;
[p1, p2, p3, p4, p5] = symbol.name.split(/\s*\/\s*/);
var name = `${p2}/${p3}/ios/ic_${p4}_${p5}pt${item.suffix}`.replace(/\s+/g, "_").toLowerCase();
// 设置缩放
exportRequest.setScale(item.scale);
// 导出资源
context.document.saveExportRequest_toFile(exportRequest, `${savePath}/${name}.png`);
}
搭建资源输出工具
现在很多平台需要特殊格式的资源,例如网页上的 Icon Font 和 SVG Sprite,Android 的 Vector Drawable 等等,这些对于一般设计师来说比较复杂,操作起来效率低下,容易因使用不同工具和无法验证等问题而出现返工。特别是在资源数量较大的情况下,例如一整套的图标、插画等等,更需要搭建一个可以自动化处理这些操作的工具。
Sketch 自动的命令行工具 sktchtool 实现了通过编程到处 Sketch 资源的可能性,只要对 Sketch 文档的图层结构合理的调整,就能非常轻松导出资源,再利用已有开源工具实现不同类型资源的转化。这些特殊格式资源都有非常成熟 Node.js 的转换工具,利用 Node.js 模块和 gulp 插件自动导出和转换各种格式资源的详细操作和代码可以查看《使用 gulp 导出 Sketch 资源》这篇文章。