[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

Android_開發者發表於2019-04-02

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

因為 Android 裝置通常具有不同的尺寸、形狀和螢幕畫素密度,所以我更喜歡用與解析度無關的向量資源(vector assets)。但它們究竟是什麼?有什麼益處?需要什麼成本?什麼時候應該使用它們?怎麼建立和使用它們?在這一系列文章中,我將會探討這些問題並解釋為什麼在你的應用中應該大量地使用向量資源(vector assets)以及怎樣最大限度地使用它們。

點陣圖 vs 向量圖

大多數的影象格式(png、jpeg、bmp、gif 和 webp 等等)都是點陣圖格式,這意味著它們將影象繪製為一個固定的畫素網格。因此,對於固定解析度的點陣圖,我們只瞭解每個畫素的顏色,卻不理解其中包含的內容。然而,向量影象是通過在抽象大小的畫布上定義一系列形狀來描繪影象。

為什麼使用向量圖?

向量資源有三大好處,分別是:

  • 好用
  • 佔用資源少
  • 動態

好用

向量圖可以優雅的調整大小;這是因為它們將影象繪製在抽象大小的畫布上,你可以放大或縮小畫布,然後重新繪製對應尺寸的影象。但是,點陣圖資源在重新調整大小後會變得很糟糕。縮小柵格資源是 OK 的(意味著會丟失一些資訊),但是放大它們會導致模糊或者色帶狀的失真,因為它們必須插入缺失的畫素。

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

放大的點陣圖(左)與放大的向量圖(右)

這就是為什麼在 Android 上我們需要為不同密度的螢幕提供多個版本的點陣圖資源:

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

在需要的時候,Android 會選擇最接近的較大密度並將其縮小。隨著裝置具有越來越高的螢幕密度,應用開發者對相同的資源必須不斷建立、囊括、轉換更多的版本。需要注意的是,許多現代裝置的螢幕密度並不是精確的(例如,Piexl 3 XL 是 552 dpi,介於 xxhdpi 和 xxxhdpi 之間),所以資源通常會被縮放。

因為向量資源可以優雅的調整大小, 你只需包含單個資源,它就能在具有任何螢幕密度的裝置上呈現。

佔用資源少

向量資源通常會比點陣圖資源佔用資源更少,因為你只需要提供一個版本,而且向量資源很好被壓縮。

例如, Google I/O app這次提交 中通過將一些 PNG 圖示從點陣圖轉換成向量圖,節約了 482 KB。儘管聽上去不是很多,但這僅僅是對小影象而言;更大的圖片(如插圖)會節省更多。

這張 插圖 來自於上一年的 Google I/O 示例 APP 流程:

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

對於插圖,向量是很好的選擇

我們無法用 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 通常會更小,並且無需建立更大的點陣圖資源。

動態

由於向量影象描述它們的內容並不是將自己”扁平化“為畫素,這為動畫、互動或動態主題等有趣的新可能開啟了新大門。將來會寫更多關於這方面的文章。

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

向量會保持影象結構,所以裡面的單個元素的屬性可以發生改變而被用來製作主題或動畫。

權衡

向量確實也有一些需要考慮的缺點:

解碼

正如前面所訴,向量影象描述了自己包含的內容,因此在使用前需要對它們進行 inflate 和 draw 操作。

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

在渲染之前解碼向量所涉及的步驟

有如下兩步:

  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 都應當使用向量資源。

適應性

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

由於格式的性質,向量在在描述一些向量資源(如簡單圖示等)時 非常有用。它們在編碼攝影型別影象時非常糟糕,因為這種影象內容很難被描述為一系列形狀的組合。點陣圖格式(如 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 元素。這個神祕的字串可以被認為是控制虛擬畫布上的筆的一系列命令:

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

視覺化路徑操作

上面的命令移動虛擬筆,然後畫一條線到另一個點,抬起並移動筆,然後繪製另一條線。 只用 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 沒有消除鋸齒。

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

宣告非抗鋸齒 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>
複製程式碼

[譯] 瞭解 Android 的向量圖片格式:`VectorDrawable`

修剪路徑

您可以從路徑的開頭或結尾進行修剪,也可以對任何修剪使用偏移。它們被定義為路徑 [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前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章