1. 矢量圖SVG簡介
Android 5.0系統(tǒng)中引入了 VectorDrawable 來支持矢量圖(SVG),同時還引入了 AnimatedVectorDrawable 來支持矢量圖動畫。
所謂SVG(Scalable Vector Graphics)韭寸,直譯為可伸縮矢量圖塑荒,具體內(nèi)容可以參考矢量圖百科撇吞。和一般的柵格圖(比如PNG)相比,雖然其繪制速度較慢梨水,卻有以下的優(yōu)點:
- 保存最少的信息老客,文件大小比位圖要小僚饭,并且文件大小與物體的大小無關(guān);
- 任意放大矢量圖形,不會丟失細(xì)節(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)簽,第一個來繪制箭頭葱弟,第二個來繪制底下的橫線壹店。細(xì)心的讀者已經(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使用的場景: