圖片壓縮知識梳理(6) VectorDrawable 及 AnimatedVectorDrawable 使用詳解

澤毛發表於2017-12-21

一、Vector優點

VectorDrawable主要有兩個優點:螢幕自適應體積小

  • 如果我們使用的是普通的png或者jpg圖片,那麼為了能讓圖片在不同解析度的機型上能夠很好地展現,通常會在各個drawable資料夾下放置不同解析度大小的圖片檔案,而VectorDrawable則不需要,僅僅只使用一套資源,就能夠適應任何解析度的手機。
  • 對於某些圖片,VectorDrawable素材的大小要比pngjpg小很多。

二、SVGVector的基本概念

下面,我們先介紹一下SVGVectorDrawable的基本概念:

  • SVG:它並不是Android平臺特有的概念,它的全稱為可縮放向量圖形,簡單的來說,就是用於描述二維向量圖形的圖形格式,更加詳細的解釋可以參考:SVG - 百度百科
  • VectorDrawable:它是Android當中的SVG實現,它並不支援SVG的全部語法,只是支援部分有必要的部分。

三、獲取VectorDrawable

俗話說的好,巧婦難為無米炊,這一節我們就來介紹一下如何獲得一個VectorDrawable,一般獲取的方式有以下三種方式:

  • 先獲取SVG素材,再通過工具轉換成為VectorDrawable
  • 通過Android Studio中的素材庫,直接獲取VectorDrawable
  • 根據Vector的語法,自己描述

3.1 先獲取SVG素材,再通過工具轉換成為VectorDrawable

首先獲取SVG素材,再將它轉換成為VectorDrawable是最常用的方式,而SVG素材的獲取又有以下幾種途徑:

  • 網站直接下載SVG影象
  • UI設計師使用專業的工具匯出
  • 通過 VectorMagic 軟體,將pngjpg素材轉換為SVG影象

對於個人開發者而言,一般都會採用第一種方式,我們這裡也只介紹第一種方式。很多文章都推薦過這個網站:iconFront - 阿里巴巴,它提供了SVGPNG素材的直接下載:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
下載完畢之後,在Android StudioNew選項中,選擇Vector Asset
圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
開啟之後,選擇Local file,並開啟我們下載後的影象,再選擇next儲存到指定的資料夾:
圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
最後,我們會得到一個*.xml檔案,這個就是VectorDrawable

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="24dp" 
    android:viewportHeight="1024.0"
    android:viewportWidth="1024.0" 
    android:width="24dp">
    <path android:fillColor="#FF000000" 
        android:pathData="M887.9,298.2c-12.9,-12.1 -33.2,-11.5 -45.2,1.4L415.9,754l-233.1,-229.7C170.2,511.9 150,512 137.5,524.6c-12.4,12.6 -12.3,32.9 0.4,45.2l256.5,252.7c0.1,0.1 0.2,0.1 0.3,0.2 0.1,0.1 0.1,0.2 0.2,0.3 2,1.9 4.4,3 6.8,4.3 1.2,0.7 2.1,1.7 3.4,2.1 3.8,1.5 7.8,2.2 11.7,2.2 4.2,0 8.4,-0.8 12.3,-2.5 1.3,-0.5 2.3,-1.7 3.6,-2.4 2.4,-1.4 4.9,-2.6 6.9,-4.7 0.1,-0.1 0.1,-0.3 0.2,-0.4 0.1,-0.1 0.2,-0.1 0.3,-0.2l449.2,-478.2C901.4,330.6 900.8,310.3 887.9,298.2z"/>
</vector>
複製程式碼

3.2 通過Android Studio中的素材庫,直接獲取VectorDrawable

還是在上面的那個介面,我們勾選另外一個選項:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
這裡面有Material Design提供的素材庫:
圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
通過這種方式,同樣可以獲取到一個*.xmlVectorDrawable

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>
複製程式碼

3.3 根據Vector的語法,自己描述

3.13.2當中,我們可以看到,最終VectorDrawable都是用一個*.xml來表示的,那麼,我們當然可以根據SVG的語法,來編寫一個xml檔案,通過pathData屬性進行描述,不過這種方式較為繁瑣,一般不會採用。

四、Vector語法

雖然說大多數情況下,我們不會完全手動去編寫Vectorxml檔案,但是,對於Vector的基本語法還是有必要了解一些的,因為在我們後邊談到的動態Vector中需要了解對於每個標籤有哪些屬性可以設定,Vector包含下面三種標籤:

<vector>
    <group>
        <path>
        <path>
    </group>
</vector>
複製程式碼

4.1 <vector>標籤

  • name:向量圖的名字
  • width, height:向量圖的固有寬高,通常使用dp
  • viewportWidth, viewportHeight:把向量圖的寬高分成多少個單元,這裡的每個單元就對應pathData中的一個點座標。

4.2 <path>標籤

  • name:路徑的名稱。
  • fillColor:圖形的填充顏色。
  • pathData:定義控制點的位置,類似與Canvas當中的Path類。

4.3 <group>標籤

<group>用來把多個<path>組合在一起,進行相同的處理。

更多的屬性可以查閱下面這兩篇文章:

VectorDrawable 詳解 Android 中的 SVG 影象的各個屬性意義

五、Vector的相容性問題

為了讓VectorDrawable能夠在Android 5.0以下版本的手機上使用,我們需要引入support包,並修改gradle的配置。

5.1 引入support

VectorDrawable是在Android 5.0之後提出來的,因此,如果我們需要在低版本上使用,那麼就要引入com.android.support:appcompat-v7包,要求版本號大於等於23.2.0,這裡我們選用的是:

compile 'com.android.support:appcompat-v7:25.3.1'
複製程式碼

Vector相關的兩個包為:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

5.2 修改gradle配置檔案

如果**gradle的版本小於2.0**:

android {
    defaultConfig {
        generatedDensities = []
    }

    aaptOptions {
        additionalParameters "--no-version-vectors"
    }
}
複製程式碼

如果**gradle的版本大於2.0**,例如我們所用的版本:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
    }
}
複製程式碼

那麼需要這樣配置:

android {
    defaultConfig {
         vectorDrawables.useSupportLibrary = true
    }
}
複製程式碼

關於更多相容性的問題,可以檢視下面這篇文章

Android Vector 曲折的相容之路

六、靜態VectorDrawable

如果使用靜態的方式展示VectorDrawable,那麼很簡單,只需要像使用普通的drawable一樣,首先,我們按照第三節的方法,獲取到一個vectorDrawable,並把它放在drawable資料夾下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>
複製程式碼

我們可以應用於:

  • ImageViewsrc
  • Viewbackground
  • TextViewdrawable
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">
    <ImageView
        android:id="@+id/iv_static"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:clickable="true"
        android:src="@drawable/ic_account_circle_selector"/>
    <View
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:clickable="true"
        android:background="@drawable/ic_account_circle_selector"/>
    <TextView
        android:drawableStart="@drawable/ic_account_circle_black_24dp"
        android:text="UserName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
複製程式碼

在上面的例子中,我們還使用了ic_account_circle_selector,其定義如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_account_circle_black_24dp" android:state_pressed="true"/>
    <item android:drawable="@drawable/ic_account_circle_black_normal_24dp"/>
</selector>
複製程式碼

ic_account_circle_black_24dp就是之前獲取到的素材,而ic_account_circle_black_normal_24dp就是改變了它的fillColor屬性:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#80000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>
複製程式碼

點選後的效果為:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

七、動態VectorDrawable

動態的VectorDrawable,也就是AnimatedVectorDrawable,一般來說,它的整個結構如下圖所示:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
為了實現動態的VectorDrawable,一般需要提供三種型別的*.xml檔案,這三個xml檔案的根標籤分別為:

  • vector:影象資源,也就是我們上面討論的靜態vector,定義於res/drawable資料夾下。
  • objectAnimator:定義影象資源中個元素的動畫行為,定義於res/anim資料夾下。
  • animated - vector:對vector中的元素與objectAnimator進行組合,定義於res/drawable資料夾下。

7.1 對<group>標籤進行動畫

下面,我們先看一個簡單的例子,上面我們所談到的三個*.xml檔案如下:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.1.1 vector檔案

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <group android:name="group_account" android:pivotX="12" android:pivotY="12">
        <path
            android:fillColor="#FF000000"
            android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
    </group>
</vector>
複製程式碼

這裡我們生成了一個頭像的Vector素材,它的影象如下,注意到,我們用一個group標籤把path標籤包裹起來,並且給它定義了一個name屬性為group_account,這個屬性在後面會用到。

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.1.2 objectAnimator檔案

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="500"
        android:propertyName="rotation"
        android:repeatCount="infinite"
        android:valueFrom="0"
        android:valueTo="360"/>
</set>
複製程式碼

objectAnimator的定義和屬性動畫是相同的,我們需要指定需要變換的屬性,也就是propertyName,並通過valueFrom/valueTo指定變化的起點值和終點值,這裡我們選擇了採用無限迴圈的方式來變換目標的rotation屬性。

7.1.3 animated-vector檔案

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/account_vector">
    <target
        android:animation="@anim/account_object_animator"
        android:name="group_account"/>
</animated-vector>
複製程式碼

animated-vectorvector是一一對應的關係,因此需要在根標籤中指定android:drawable,也就是在7.1.1中的vector檔案。 而每一個target標籤是內是成對的name - animation屬性,name就是在7.1.1中宣告的groupname,而animation則是在7.1.2中定義的動畫檔案。

如果objectAnimator所指定的屬性在name所對應的標籤中沒有,那麼不會發生任何變化。

7.1.4 啟動動畫

首先,在佈局的xml檔案中,把imageViewsrc指定為7.1.3中的animate-vector

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">
    <ImageView
        android:id="@+id/iv_dynamic"
        android:src="@drawable/account_animated_vector"
        android:text="UserName"
        android:layout_width="50dp"
        android:layout_height="50dp"/>
</LinearLayout>
複製程式碼

在程式碼中,需要手動獲得這個vectorDrawable,然後啟動動畫:

public class VectorDrawableActivity extends AppCompatActivity {

    private ImageView mAnimateView;
    private AnimatedVectorDrawable mAnimatedVectorDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_vector_drawable);
        mAnimateView = (ImageView) findViewById(R.id.iv_dynamic);
        mAnimatedVectorDrawable = (AnimatedVectorDrawable) mAnimateView.getDrawable();
        mAnimateView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mAnimatedVectorDrawable.start();
            }
        });
    }
}
複製程式碼

效果如下:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.2 對<path>標籤中的屬性進行動畫

7.1中,演示瞭如何對group標籤的屬性進行變換,下面,我們再演示一下如何對path標籤中的屬性進行變換。和前面類似,我們依然需要三種類性的*.xml檔案

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.2.1 vector檔案

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:name="path_check"
        android:fillColor="#FF000000"
        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>
複製程式碼

這裡,同樣需要給path指定一個名字,用於後面和objectAnimator進行關聯,它的素材為:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.2.2 objectAnimator檔案

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="500"
        android:propertyName="trimPathEnd"
        android:valueFrom="0"
        android:valueTo="1"
        android:valueType="floatType"/>
</set>
複製程式碼

這裡,我們選擇的是path標籤下的trimPathEnd屬性,它表示從Path終點的位置去除Path,與之對應的還有trimPathStart屬性,它表示從Path起點的位置去除Path,而trimPathOffset則可以改變Path的起點,這三個值的取值範圍都是0~1

7.2.3 animated-vector檔案

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/check_vector">
    <target
        android:animation="@anim/check_object_animator"
        android:name="path_check"/>
</animated-vector>
複製程式碼

效果如下:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.3 <path>之間切換

除了上面普通的屬性之外,還支援對<path>標籤下的<pathData>進行改變,系統會比較兩個pathData之間的差別,並自動產生合適的動畫。需要注意,它要求這兩個pathData的點座標的個數是相同的

7.3.1 Vector檔案

這裡,我們需要生成兩個vectorDrawable,首先是起始的VectorDrawable

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:name="path_arrow_down"
        android:fillColor="#FF000000"
        android:pathData="M7,10l5,5 5,-5z"/>
</vector>
複製程式碼

它的素材為:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解
接著是終點的VectorDrawable

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M7,14l5,-5 5,5z"/>
</vector>
複製程式碼

它對應的素材為:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

7.3.2 objectAnimator檔案

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="500"
        android:propertyName="pathData"
        android:valueFrom="M7,10l5,5 5,-5z"
        android:valueTo="M7,14l5,-5 5,5z"
        android:valueType="pathType"/>
</set>
複製程式碼

這裡的propertyNamevalueType需要分別定義為pathDatapathType,而起點和終點的值就是我們在7.3.1生成的兩個VectorDrawable所對應的pathData屬性的值。

7.3.3 animated-vector檔案

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/arrow_down_vector">
    <target
        android:animation="@anim/arrow_down_object_animator"
        android:name="path_arrow_down"/>
</animated-vector>
複製程式碼

最終的效果為:

圖片壓縮知識梳理(6)   VectorDrawable 及 AnimatedVectorDrawable 使用詳解

八、VectorDrawable的效能

關於VectorDrawable的效能問題,Android Vector 曲折的相容之路 這篇文章說的很好,因此直接引用過來了:

  • Bitmap的繪製效率並不一定會比Vector高,它們有一定的平衡點,當Vector比較簡單時,其效率是一定比Bitmap高的,所以,為了保證Vector的高效率,Vector需要更加簡單,PathData更加標準、精簡,當Vector影象變得非常複雜時,就需要使用Bitmap來代替了。
  • Vector適用於ICONButtonImageView的圖示等小的ICON,或者是需要的動畫效果,由於BitmapGPU中有快取功能,而Vector並沒有,所以Vector影象不能做頻繁的重繪
  • Vector影象過於複雜時,不僅僅要注意繪製效率,初始化效率也是需要考慮的重要因素
  • SVG載入速度會快於PNG,但渲染速度會慢於PNG,畢竟PNG有硬體加速,但平均下來,載入速度的提升彌補了繪製的速度缺陷。

九、參考文獻

1. Android Vector 曲折的相容之路 2. VectorDrawable 怎麼玩 3. Android 使用向量圖(SVG, VectorDrawable)實踐篇 4. SVG - 百度百科 5. Android 中的 SVG 影象的各個屬性意義

相關文章