Android動畫之VectorDrawable矢量圖實戰(zhàn)

效果圖

1. 矢量圖SVG簡介

Android 5.0系統(tǒng)中引入了 VectorDrawable 來支持矢量圖(SVG),同時還引入了 AnimatedVectorDrawable 來支持矢量圖動畫。

所謂SVG(Scalable Vector Graphics),直譯為可伸縮矢量圖,具體內(nèi)容可以參考矢量圖百科。和一般的柵格圖(比如PNG)相比,雖然其繪制速度較慢,卻有以下的優(yōu)點:

  • 保存最少的信息,文件大小比位圖要小,并且文件大小與物體的大小無關(guān);
  • 任意放大矢量圖形,不會丟失細節(jié)或影響清晰度,因為矢量圖形是與分辨率無關(guān)的。

從以上兩個優(yōu)點來看,在項目中使用矢量圖至少可以縮小我們apk包的尺寸,而且可以在屏幕適配時提供很大的方便,因為矢量圖是分辨率無關(guān)的。

2. Android中的Vector

SVG是一套標(biāo)準(zhǔn),VectorDrawable是Android中的實現(xiàn),但是VectorDrawable 并沒有支持所有的 SVG 規(guī)范,目前只支持 PathData 和有限的 Group 功能。 所以對于使用 VectorDrawable 而言,我們只需要了解 SVG 的 PathData 規(guī)范即可。通過查看 PathData 文檔,可以看到 path 數(shù)據(jù)包含了一些繪圖命令,比如 :

  • M: move to 移動繪制點;
  • L:line to 直線;
  • Z:close 閉合;
  • C:cubic bezier 三次貝塞爾曲線;
  • Q:quatratic bezier 二次貝塞爾曲線;
  • A:ellipse 圓弧;

每個命令都有大小寫形式,大寫代表后面的參數(shù)是絕對坐標(biāo),小寫表示相對坐標(biāo)。參數(shù)之間用空格或逗號隔開
命令詳解:

  • M (x y) 移動到x,y;
  • L (x y) 直線連到x,y,還有簡化命令H(x) 水平連接、V(y)垂直連接;
  • Z,沒有參數(shù),連接起點和終點;
  • C(x1 y1 x2 y2 x y),控制點x1,y1 x2,y2,終點x,y;
  • Q(x1 y1 x y),控制點x1,y1,終點x,y;
  • A(rx ry x-axis-rotation large-arc-flag sweep-flag x y) :
    • rx ry 橢圓半徑
    • x-axis-rotation x軸旋轉(zhuǎn)角度
    • large-arc-flag 為0時表示取小弧度,1時取大弧度
    • sweep-flag 0取逆時針方向,1取順時針方向

以下是繪制三角形的VectorDrawable實例:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" 
    android:height="64dp"
    android:width="64dp"
    android:viewportHeight="100"
    android:viewportWidth="100">
    
    <path
         android:fillColor="#000000"
         android:pathData="M25,0 l 50,50 -50,50Z"/>

</vector>

首先vector 標(biāo)簽是一個drawable對象,所以是放在res/drawable目錄的。

vector 標(biāo)簽下有android:widthandroid:height屬性,這兩個屬性是必填的,定義矢量圖形的絕對大小,雖然說是矢量圖形隨意縮放,但是不能說這里不定義寬高直接到要設(shè)置到的目標(biāo)控件上定義控件的寬高,這樣是不允許的,一定要設(shè)置這個絕對寬高,要不然會報錯。

然后還有個android:viewportHeightandroid:viewportWidth屬性,這個是畫布寬高,也是必填的,定義Path路徑的時候就必須在這個畫布大小里去繪制,超出畫布就顯示不出來了。

path標(biāo)簽android:fillColor屬性定義繪制顏色,android:pathData定義繪制路徑。

M25,0 l 50,50 -50,50Z這個路徑表示:

  1. 在100*100的畫布內(nèi),先把繪制點移動到絕對坐標(biāo)(25,0)這個點,然后畫直線到(50,50)這個點,l指令是相對坐標(biāo),大寫的L表示絕對坐標(biāo),那么l 50,50就是在原點(25,0)的x軸往前移50,往下移50,絕對坐標(biāo)就是(75,50),也就是三角形的右邊那個點;
  2. 然后從(50,50)這個點繪制到三角形最下面那個點(-50,50),這也是相對右邊那個點相對坐標(biāo),也就是把(75,50)這個絕對坐標(biāo)當(dāng)作是原點(0,0),參作這個原點往后移動50再往下移動50,在整個畫布中的絕對坐標(biāo)就是(25,100)。

效果如下:


繪制出的三角形

如果要繪制標(biāo)準(zhǔn)的SVG,可以是使用在線SVG Editor
另外,如果需要將SVG轉(zhuǎn)化為VectorDrawable,還可以是使用在線工具Android SVG to VectorDrawable

更為重要的是,Android Studio自帶Vector Assert工具,可以幫助我們生成矢量圖;

  • Material icon:使用官方自帶的各種矢量圖,內(nèi)容很豐富;
  • Local SVG file:使用第三方的SVG文件,比如從第三方圖標(biāo)網(wǎng)站:iconfont.cn中下載的圖片都支持SVG;

3. Vector的配置和兼容性

正如上文所說,Android L開始提供了新的API VectorDrawable
可以使用SVG類型的資源,最初只能在21以上版本中使用。

為了讓低版本也可以支持矢量圖,Gradle Plugin 1.5加入了如下功能:

  • buildVersion>=21時,Vector矢量圖功能不變;
  • buildVersion<21時,編譯時,自動轉(zhuǎn)換把Vector矢量圖轉(zhuǎn)化為PNG;

再后來Google升級了support library,官方向后兼容了矢量圖的使用。矢量圖兼容到API7,矢量圖動畫兼容到API11。

3.1 appcompat-v:23.2.0

因此要在低版本上兼容矢量圖,就需要在項目中引入新的兼容庫support-vector-drawable,并且appcompat-v7庫的版本要在23.2.0+

compile 'com.android.support:appcompat-v7:23.2.0'

3.2 gradle

而且你還要修改下gradle的相關(guān)配置,不要讓gradle在構(gòu)建的時候為你在低版本(API21以下)的情況下生成針對于不同密度的png文件,因為android studio1.4的時候支持了矢量圖。

如果你的gradle插件的版本為2.0以下,你應(yīng)該這么修改:

android {
  defaultConfig {
    // 不讓gradle自動生成不同屏幕分辨率的png圖
    generatedDensities = []
  }
  aaptOptions {
    additionalParameters "--no-version-vectors"
  }
}

現(xiàn)在大部分人的gradle插件版本是2.0+,只要這樣修改就好:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

經(jīng)過上面這幾步的修改,就可以在項目中使用矢量圖了。下面我們就正式來說說怎么使用。

4. Vector和屬性動畫結(jié)合使用

先看下效果圖,這是一個代表文件下載的圖片,是通過矢量圖繪制的。我們通過將其和屬性動畫結(jié)合,實現(xiàn)了下載過程中的動畫效果:箭頭跳躍+橫線反復(fù)出現(xiàn)


FileDownloading.gif

4.1 Vector矢量圖

首先我們看一下圖標(biāo)的矢量圖:

<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="arrow_location">
        <path
            android:name="arrow_pic"
            android:fillColor="#FF000000"
            android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7z"/>
        </group>

    <path
        android:name="bar"
        android:fillColor="#FF000000"
        android:pathData="M5,18v2h14v-2H5z"/>

</vector>

矢量圖中有兩個Path標(biāo)簽,第一個來繪制箭頭,第二個來繪制底下的橫線。細心的讀者已經(jīng)發(fā)現(xiàn),第一個Path標(biāo)簽外被Group標(biāo)簽包裹,這是因為path標(biāo)簽中沒有坐標(biāo)變化的屬性,這些屬性在group標(biāo)簽中,要使用這些標(biāo)簽就必須在group中繪制。

4.2 ObejctAnimator

Android屬性動畫Animator實現(xiàn)衛(wèi)星Button中已經(jīng)介紹了關(guān)于如何動態(tài)使用屬性動畫。本文的屬性動畫將以靜態(tài)XML文件的出現(xiàn),但是其內(nèi)容是一樣的。

如有要實現(xiàn)箭頭跳躍的動畫,就要讓箭頭的"translateY"屬性向上移動,然后反復(fù)執(zhí)行。為了讓移動更有跳躍的感覺,interpolator被設(shè)為overshoot
“arrow_jump.xml”文件如下:

<set xmlns:androd="http://schemas.android.com/apk/res/android">
    <objectAnimator
        androd:duration="500"
        androd:interpolator="@android:interpolator/overshoot"
        androd:propertyName="translateY"
        androd:repeatCount="infinite"
        androd:repeatMode="reverse"
        androd:valueFrom="0"
        androd:valueTo="-3"
        androd:valueType="floatType"
        >
    </objectAnimator>
</set>

"bar_drawing.xml"實現(xiàn)動畫下方橫線反復(fù)出現(xiàn)的過程。

<set xmlns:androd="http://schemas.android.com/apk/res/android">
    <objectAnimator
        androd:duration="500"
        androd:propertyName="trimPathEnd"
        androd:repeatCount="infinite"
        androd:repeatMode="reverse"
        androd:valueFrom="0"
        androd:valueTo="1"
        androd:valueType="floatType">
    </objectAnimator>
</set>

這里要特別說明下屬性* trimPathEnd,另外還有個屬性 trimStartEnd*,其中trim是截取的意思,PathStart代表開始時的圖像,PathEnd代表結(jié)束時的圖像,變化的初始值和終值代表繪制的比率,從0變化到1,也就代表的是從完全沒有到完全繪制出來,即從無到有。

持外兩個屬性屬性動畫最外層都是set標(biāo)簽,用戶可以根據(jù)自己設(shè)計加入更多的屬性動畫標(biāo)簽ObjectAnimator

4.3 animated-vector 粘合劑

如何將矢量圖和屬性動畫結(jié)合起來呢,這就需要們的粘合劑*animated-vector *文件,在其中建立矢量圖和動畫的對應(yīng)關(guān)系,如下:
“file_download.xml"

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_file_download_black_24dp">
    <target
        android:animation="@animator/arrow_jump"
        android:name="arrow_location"/>

    <target
        android:animation="@animator/bar_drawing"
        android:name="bar"/>
</animated-vector>

其中animated-vectorandroid:drawable屬性代表要控制的對象是哪一張矢量圖。targe標(biāo)簽代表的動畫和對象的一個對應(yīng)關(guān)系,animation代表要加載的動畫文件,name代表矢量圖文件中的哪一個部分。這里兩個target分別給。

更需要注意的是,位置移動所操控的屬性并不是矢量圖中path標(biāo)簽的,而是group標(biāo)簽的內(nèi)容,所以箭頭的name標(biāo)準(zhǔn)建立在group之中,arrow_jump動畫綁定的對象也是group。同時,* trimPathEnd屬性是Path的屬性,所以動畫綁定對象應(yīng)該是Path*。
4.4 布局和模擬
Activity中的布局如下:

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/image"
            android:layout_marginTop="5dp"
            android:onClick="anim"
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:srcCompat="@drawable/file_download"/>
    </LinearLayout>
</ScrollView>

需要注意的是,ImageViewapp:srcCompat被設(shè)為粘合劑文件” file_download“。另外因為使用了app:srcCompat這個自定義的屬性,需要加入命名空間xmlns:app="http://schemas.android.com/apk/res-auto"

Activity中對應(yīng)的源碼也很簡單,點擊圖片模擬下載過程并開始動畫,5秒之后下載結(jié)束,停止動畫。

import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import static java.lang.Thread.sleep;

public class MainActivity extends AppCompatActivity {
    ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView = (ImageView) findViewById(R.id.image);
    }

        
    public void anim(View view) {//圖片點擊事件
        ImageView imageView = (ImageView) view;
        Drawable drawable = imageView.getDrawable();//獲得圖片的Drawable屬性
        if(drawable instanceof Animatable) {//如果是屬性動畫
            ((Animatable) drawable).start(); //開始動畫
            Toast.makeText(view.getContext(), "下載開始!",Toast.LENGTH_SHORT).show();
        }
        new Thread(new Runnable() {//開啟計時線程
            @Override
            public void run() {
                try {
                    sleep(5000); //睡眠5s,代表下載過程
                    runOnUiThread(new Runnable() {//在UI線程中停止動畫
                        @Override
                        public void run() {
                            ((Animatable) mImageView.getDrawable()).stop();
                            Toast.makeText(getApplicationContext(),"下載完畢",Toast.LENGTH_SHORT).show();
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

5. VectorDrawable的一些兼容問題:

如果使用appcompat-v:23.2.0+,兼容的API版本有比Android 5小的情況,還要注意有一些兼容新的問題:

  1. 在5.0以下的版本中,還無法使用Path Morphing(路徑變化)屬性;
  2. 在5.0以下的版本中,只能使用系統(tǒng)插值器;
  3. 在5.0以上版本中,需要把Drawable對象轉(zhuǎn)化成AnimatedVectorDrawable之后才能使用
    public void animL(View view) {
        ImageView imageView = (ImageView) view;
        AnimatedVectorDrawable drawable = (AnimatedVectorDrawable) getDrawable(R.drawable.fivestar_anim);
        imageView.setImageDrawable(drawable);
        if (drawable != null) {
            drawable.start();
        }
    }

當(dāng)然,如果最小版本都是5以上的,就沒有這些問題了。

6. VectorDrawable的使用場景

最后借用前輩"徐醫(yī)生"總結(jié)的VectorDrawable和Bitmap對比,為介大家紹適合VectorDrawable使用的場景:


VectorDrawable和Bitmap對比

7. 參考文獻與閱讀擴展

  1. Android VectorDrawable與SVG
  2. VectorDrawable怎么玩
  3. Androidの矢量圖形之VectorDrawable研究
  4. Android 5.0學(xué)習(xí)之AnimatedVectorDrawable
  5. VectorDrawable詳解
  6. Android屬性動畫Animator實現(xiàn)衛(wèi)星Button
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容