[译] 了解 Android 的矢量图片格式:`VectorDrawable`

由于 Android 设备一般具备不一样的尺寸、形状和屏幕像素密度,因此我更喜欢用与分辨率无关的矢量资源(vector assets)。但它们到底是什么?有什么益处?须要什么成本?何时应该使用它们?怎么建立和使用它们?在这一系列文章中,我将会探讨这些问题并解释为何在你的应用中应该大量地使用矢量资源(vector assets)以及怎样最大限度地使用它们。javascript

位图 vs 矢量图

大多数的图像格式(png、jpeg、bmp、gif 和 webp 等等)都是位图格式,这意味着它们将图像绘制为一个固定的像素网格。所以,对于固定分辨率的位图,咱们只了解每一个像素的颜色,却不理解其中包含的内容。然而,矢量图像是经过在抽象大小的画布上定义一系列形状来描绘图像。html

为何使用矢量图?

矢量资源有三大好处,分别是:前端

  • 好用
  • 占用资源少
  • 动态

好用

矢量图能够优雅的调整大小;这是由于它们将图像绘制在抽象大小的画布上,你能够放大或缩小画布,而后从新绘制对应尺寸的图像。可是,位图资源在从新调整大小后会变得很糟糕。缩小栅格资源是 OK 的(意味着会丢失一些信息),可是放大它们会致使模糊或者色带状的失真,由于它们必须插入缺失的像素。java

放大的位图(左)与放大的矢量图(右)android

这就是为何在 Android 上咱们须要为不一样密度的屏幕提供多个版本的位图资源:ios

  • res/drawable-mdpi/foo.png
  • res/drawable-hdpi/foo.png
  • res/drawable-xhdpi/foo.png

在须要的时候,Android 会选择最接近的较大密度并将其缩小。随着设备具备愈来愈高的屏幕密度,应用开发者对相同的资源必须不断建立、囊括、转换更多的版本。须要注意的是,许多现代设备的屏幕密度并非精确的(例如,Piexl 3 XL 是 552 dpi,介于 xxhdpi 和 xxxhdpi 之间),因此资源一般会被缩放。git

由于矢量资源能够优雅的调整大小, 你只需包含单个资源,它就能在具备任何屏幕密度的设备上呈现。github

占用资源少

矢量资源一般会比位图资源占用资源更少,由于你只须要提供一个版本,并且矢量资源很好被压缩。web

例如, Google I/O app此次提交 中经过将一些 PNG 图标从位图转换成矢量图,节约了 482 KB。尽管听上去不是不少,但这仅仅是对小图像而言;更大的图片(如插图)会节省更多。后端

这张 插图 来自于上一年的 Google I/O 示例 APP 流程:

对于插图,矢量是很好的选择

咱们没法用 VectorDrawable 替换它,由于当时没有普遍支持渐变(如今已经支持),因此咱们不得不发布一个位图版本 😔。若是咱们可以使用矢量,那么这将只有其大小的 30%,并且会取得更好的效果:

  • Raster: Download Size = 53.9KB (Raw file size = 54.8KB)
  • Vector: Download Size = 3.7KB (Raw file size = 15.8KB)

请注意,虽然 Android App Bundle 经过向不一样设备提供其所需的密度资源带来相同的好处,但 VectorDrawable 一般会更小,而且无需建立更大的位图资源。

动态

因为矢量图像描述它们的内容并非将本身”扁平化“为像素,这为动画、交互或动态主题等有趣的新可能打开了新大门。未来会写更多关于这方面的文章。

矢量会保持图像结构,因此里面的单个元素的属性能够发生改变而被用来制做主题或动画。

权衡

矢量确实也有一些须要考虑的缺点:

解码

正如前面所诉,矢量图像描述了本身包含的内容,所以在使用前须要对它们进行 inflate 和 draw 操做。

在渲染以前解码矢量所涉及的步骤

有以下两步:

  1. Inflation。你的矢量文件必须被读取和解析成为 [VectorDrawable](https://developer.android.com/reference/android/graphics/drawable/VectorDrawable) 对你声明的 pathsgroups 进行建模。
  2. Drawing。而后必须经过执行 Canvas 绘制命令来绘制这些模型对象。

这两步的执行时间与矢量的复杂性和你执行的操做类型成正比。若是你使用很是复杂的形状,将会花费更长的时间将之解析成为 [Path](https://developer.android.com/reference/android/graphics/Path)。相似地,更多的绘制操做将花费更长的时间来执行(还有一些更耗费时间的,例如剪辑操做)。

对于静态矢量,绘图阶段只需执行一次,而后能够缓存为 Bitmap。对于动画矢量,就没法进行此优化,由于它们的属性必然会发生变化,须要从新绘制。

将其与像 PNG 这样只须要解码文件内容的位图资源进行比较,这些资源随着时间的推移已经通过高度优化。

这是位图与矢量图的基本权衡。矢量图提供上述好处,但代价是渲染更加昂贵。在 Android 早期, 设备性能差一点,屏幕密度差异不大。如今,Android 设备性能愈来愈好,屏幕密度却各不相同。所以我认为全部 APP 都应当使用矢量资源。

适应性

因为格式的性质,矢量在在描述一些矢量资源(如简单图标等)时 很是有用。它们在编码摄影类型图像时很是糟糕,由于这种图像内容很难被描述为一系列形状的组合。位图格式(如 webp)此时会更有效率。这固然是一个范围,取决于你的资源的复杂度。

转变

据我所知,没有设计工具可以直接建立 VectorDrawables ,这意味着有一个来自其余格式的转换步骤。 这会使设计人员和开发人员之间的工做流程复杂化。咱们将在之后的文章中深刻讨论这个主题。

为何不用 SVG?

若是你曾经使用矢量图像格式,你可能会遇到网络上的行业标准 SVG 格式(可缩放矢量图形)。它是强大、成熟的建模工具,它同时也是一个强大的标准。它包括许多复杂的功能,如执行任意 javascript,模糊和滤镜效果或嵌入其余图像,甚至 GIF 动画。Android 在受限制的移动设备上运行,所以支持整个 SVG 规范并非一个现实的目标。

然而,SVG 包含一个 路径规范,它定义了如何描述和绘制形状。使用此 API,您能够表达大多数矢量形状。这基本上和Android 支持的 SVG 路径规范相同,只不过Android中增长了一些内容。

此外,经过定义本身的格式,VectorDrawable 能够与 Android 平台功能集成。例如,使用 Android 资源系统引用 @colors、@dimens 或 @strings,使用标准 Animators 处理主题属性或 AnimatedVectorDrawable。

VectorDrawable 的功能

如上所述,VectorDrawable 支持 SVG 路径规范,容许您指定要绘制的一个或多个形状。它是经过 XML 文件实现的,以下所示:

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:width="24dp"
  android:height="24dp"
  android:viewportWidth="24"
  android:viewportHeight="24">

    <path
      android:name="cross"
      android:pathData="M6.4,6.4 L17.6,17.6 M6.4,17.6 L17.6,6.4"
      android:strokeWidth="2"
      android:strokeLineCap="square"
      android:strokeColor="#999" />

</vector>
复制代码

请注意,您须要指定资源的固有大小,即经过 ImageView 的 wrap_content 设置它的大小。第二个 视口 大小定义虚拟画布,或者定义全部后续绘制命令的空间坐标。固有和视口尺寸能够不一样(但应该以相同的比例)— 若是你须要,能够在 1*1 画布中定义矢量。

<vector> 元素包含一个或多个 <path> 元素。它们能够被命名(以供稍后参考,例如动画),但相当重要的是必须指定描述形状的 pathData 元素。这个神秘的字符串能够被认为是控制虚拟画布上的笔的一系列命令:

可视化路径操做

上面的命令移动虚拟笔,而后画一条线到另外一个点,抬起并移动笔,而后绘制另外一条线。 只用 4 个最经常使用的命令,咱们几乎能够描述任何形状(更多的命令参见 规范):

  • M move to
  • L line to
  • C (cubic bezier) curve to
  • Z close (line to first point)

(大写命令使用绝对路径 & 小写命令使用相对路径)

你可能想知道是否须要关注这些细节 — 你可能直接从 SVG 文件中获取这些内容?你虽然不须要经过阅读路径来了解它将绘制什么,但大概了解VectorDrawable 正在作什么对于理解咱们稍后将要学习的一些高级功能很是有用和必要。

路径自己不会绘制任何东西,它们须要被 stroke 或 fill。

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...>

    <path
      android:pathData="..."
      android:fillColor="#ff00ff"
      android:strokeColor="#999"
      android:strokeWidth="2"
      android:strokeLineCap="square" />

</vector>
复制代码

本系列的第 2 部分详细介绍了填充和描边路径的不一样方法。

你还能够定义路径组。这容许你定义应用于组内全部路径的转换操做。

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...>

    <path .../>

    <group
        android:name="foo"
        android:pivotX="12"
        android:pivotY="0"
        android:rotation="45"
        android:scaleX="1.2"
        android:translateY="-4">

        <path ... />

    </group>

</vector>
复制代码

请注意,你没法旋转、缩放、转化单个路径。若是你想要这种行为,则须要将它们放在一个组中。这些变换对静态图像毫无心义,由于静态图像能够直接将它们“烘焙”到它们的路径中 — 但它们对于动画很是有用。

您还能够定义 clip-path,即屏蔽 同一组 中其余路径能够绘制的区域。它们的定义与 path 彻底相同。

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...>
  
  <clip-path
    android:name="mask"
    android:pathData="..." />

  <path .../>

</vector>
复制代码

值得注意的一个限制是 clip-path 没有消除锯齿。

声明非抗锯齿 clip path

这个例子(我必须放大以显示效果)显示了两种绘制相机快门图标的方法。第一个绘制路径,第二个绘制一个实心方块,屏蔽快门形状。遮罩能够帮助建立有趣的效果(特别是在动画时),但它成本相对较高,因此你须要以不一样的方式绘制形状来避免它。

路径能够修剪;这只是绘制整个路径的一个子集。你能够修剪填充的路径,但结果可能会使人惊讶!修剪描边路径更常见。

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector...>

  <path
    android:pathData="..."
    android:trimPathStart="0.1"
    android:trimPathEnd="0.9" />

</vector>
复制代码

修剪路径

您能够从路径的开头或结尾进行修剪,也能够对任何修剪使用偏移。它们被定义为路径 [0,1] 的一部分。了解如何设置不一样的修剪值会更改绘制线条的部分。另请注意,偏移可使修剪值“环绕”。再一个,这个属性对静态图像没有多大意义,但对动画很方便。

矢量元素支持 alpha 属性 [0, 1]。Group 没有 alpha 属性,但各个路径支持 fillAlphastrokeAlpha

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...
  android:alpha="0.7">

  <path
    android:pathData="..."
    android:fillColor="#fff"
    android:fillAlpha="0.5" />

</vector>
复制代码

后续工做

因此但愿这篇文章可让您了解什么是矢量资源、使用矢量资源的好处以及使用时的权衡取舍。Android 的矢量格式已经获得普遍的支持。鉴于市场上的设备种类繁多,你应该将矢量资源做为默认选择,仅在特殊状况下使用位图资源。阅读咱们的下一篇文章,了解更多信息:

即将到来: 绘制路径
即将到来: 建立Android矢量资源
即将到来: 在 Android 应用中使用 vector assets
即将到来:分析 Android 中的 VectorDrawable

感谢 Ben WeissJose AlcérrecaChris Banes

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索