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:width和android:height屬性,這兩個屬性是必填的,定義矢量圖形的絕對大小,雖然說是矢量圖形隨意縮放,但是不能說這里不定義寬高直接到要設(shè)置到的目標(biāo)控件上定義控件的寬高,這樣是不允許的,一定要設(shè)置這個絕對寬高,要不然會報錯。
然后還有個android:viewportHeight和android:viewportWidth屬性,這個是畫布寬高,也是必填的,定義Path路徑的時候就必須在這個畫布大小里去繪制,超出畫布就顯示不出來了。
path標(biāo)簽android:fillColor屬性定義繪制顏色,android:pathData定義繪制路徑。
M25,0 l 50,50 -50,50Z這個路徑表示:
- 在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),也就是三角形的右邊那個點;
- 然后從(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)
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-vector的android: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>
需要注意的是,ImageView的app: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小的情況,還要注意有一些兼容新的問題:
- 在5.0以下的版本中,還無法使用Path Morphing(路徑變化)屬性;
- 在5.0以下的版本中,只能使用系統(tǒng)插值器;
- 在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使用的場景: