[譯] 瞭解 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索