兩年前寫書的時候,就在研究Android L提出的Vector,可研究下來發現,徹底不具有兼容性,相信這也是它沒有被普遍使用的一個緣由,通過Google的不懈努力,如今Vector終於迎來了它的春天。html
在文章後面,會給出本文的Demo和效果圖,並開源在Github前端
Android 5.0發佈的時候,Google提供了Vector的支持。Vector Drawable相對於普通的Drawable來講,有如下幾個好處:android
Vector圖像剛發佈的時候,是隻支持Android 5.0+的,對於Android pre-L的系統來講,並不能使用,因此,能夠說那時候的Vector並無什麼卵用。不過自從AppCompat 23.2以後,Google對p-View的Android系統也進行了兼容,也就是說,Vector可使用於Android 2.1以上的全部系統,只須要引用com.android.support:appcompat-v7:23.2.0以上的版本就能夠了,這時候,Vector應該算是迎來了它的春天。git
首先,須要講解兩個概念——SVG和Vector。程序員
SVG,即Scalable Vector Graphics 矢量圖,這種圖像格式在前端中已經使用的很是普遍了,詳見WIKI:https://en.wikipedia.org/wiki/Scalable_Vector_Graphicsgithub
Vector,在Android中指的是Vector Drawable,也就是Android中的矢量圖,詳見:https://developer.android.com/reference/android/graphics/drawable/VectorDrawable.htmlc#
所以,能夠說Vector就是Android中的SVG實現,由於Android中的Vector並非支持所有的SVG語法,也沒有必要,由於完整的SVG語法是很是複雜的,但已經支持的SVG語法已經夠用了,特別是Path語法,幾乎是Android中Vector的標配,詳細能夠參考:http://www.w3.org/TR/SVG/paths.html數組
Android以一種簡化的方式對SVG進行了兼容,這種方式就是經過使用它的Path標籤,經過Path標籤,幾乎能夠實現SVG中的其它全部標籤,雖然可能會複雜一點,但這些東西都是能夠經過工具來完成的,因此,不用擔憂寫起來會很複雜。android-studio
Path指令解析以下所示:緩存
支持的指令:
使用原則:
注意,’M’處理時,只是移動了畫筆, 沒有畫任何東西。 它也能夠在後面給出上同時繪製不連續線。
關於這些語法,開發者須要的並非所有精通,而是可以看懂便可,其它的均可以交給工具來實現。
要從通常使用的PNG圖像轉換到SVG圖像,對於設計師來講,並非一件難事,由於大部分的設計工具(PS、Illustrator等等)都支持導出各類格式的圖像,如PNG、JPG,固然,也包括SVG,所以,設計師能夠徹底按照原有的方式進行設計,只是最後導出的時候,選擇SVG便可。
不要求開發者都去學習使用這些設計工具,開發者能夠利用一些工具,本身轉換一些比較基礎的圖像,http://inloop.github.io/svg2android/ 就是這樣一個很是牛逼的網站,能夠在線將普通圖像轉換爲Android Vector Drawable。如圖所示:
或者,還可使用SVG的編輯器來進行SVG圖像的編寫,例如http://editor.method.ac/
利用Android Studio的Vector Asset,能夠很是方便的建立Vector圖像,甚至能夠直接經過本地的SVG圖像來生成Vector圖像,如圖所示:
進去以後,就能夠生成Vector圖像,如圖所示:
Vector是在Android L中提出來的新概念,因此在剛開始的時候是隻兼容L+的。
從Gradle Plugin 1.5開始,Google支持了一種兼容方式,即在Android L之上,使用Vector,而在L之下,則使用Gradle將Vector生成PNG圖像。
Android gradle plugin 1.5發佈之後,加入了一個跟VectorDrawable有關的新功能。Android build tools 提供了另一種解決兼容性的方案,若是編譯的版本是5.0以前的版本,那麼build tools 會把VectorDrawable生成對應的png圖片,這樣在5.0如下的版本則使用的是生成的png圖,而在5.0以上的版本中則使用VectorDrawable.在build.gradle添加generatedDensities配置,能夠配置生成的png圖片的密度。
從AppCompat23.2開始,Google開始支持在低版本上使用Vector。
咱們有不少方法可以獲得這些Vector,那麼如何使用它們呢,Android 5.0以上的使用就不講了,不太具備廣泛表明性,咱們從pre-L版本的兼容開始作起。
VectorDrawableCompat依賴於AAPT的一些功能,它能保持最近矢量圖使用的添加的屬性ID,以便他們能夠被pre-L版本以前的引用。
在Android 5.0以前使用Vector,須要aapt來對資源進行一些處理,這一過程能夠在aapt的配置中進行設置,若是沒有啓用這樣一個flag,那麼在5.0如下的設備上運行就會發生android.content.res.Resources$NotFoundException。
首先,你須要在項目的build.gradle腳本中,增長對Vector兼容性的支持,代碼以下所示:
使用Gradle Plugin 2.0以上:
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true } }
使用Gradle Plugin 2.0如下,Gradle Plugin 1.5以上:
android {
defaultConfig {
// Stops the Gradle plugin’s automatic rasterization of vectors generatedDensities = [] } // Flag to tell aapt to keep the attribute ids around aaptOptions { additionalParameters "--no-version-vectors" } }
像前面提到的,這種兼容方式其實是先關閉AAPT對pre-L版本使用Vector的妥協,即在L版本以上,使用Vector,而在pre-L版本上,使用Gradle生成相應的PNG圖片,generatedDensities這個數組,實際上就是要生成PNG的圖片分辨率的數組,使用appcompat後就不須要這樣了。
固然,最重要的仍是添加appcompat的支持:
compile 'com.android.support:appcompat-v7:23.4.0'
同時,確保你使用的是AppCompatActivity而不是普通的Activity。
一個基本的Vector圖像,實際上也是一個xml文件,以下所示:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="200dp" android:height="200dp" android:viewportHeight="500" android:viewportWidth="500"> <path android:name="square" android:fillColor="#000000" android:pathData="M100,100 L400,100 L400,400 L100,400 z"/> </vector>
顯示如圖所示:
這裏須要解釋下這裏的幾個標籤:
這樣作有一個很是好的做用,就是將圖像大小與圖像分離,後面能夠隨意修改圖像大小,而不須要修改PathData中的座標。
有了靜態的Vector圖像,就能夠在控件中使用了。
能夠發現,這裏咱們使用的都是普通的ImageView,好像並非AppcomatImageView,這是由於使用了Appcomat後,系統會自動把ImageView轉換爲AppcomatImageView。
對於ImageView這樣的控件,要兼容Vector圖像,只須要將以前的android:src屬性,換成app:srcCompat便可,示例代碼以下所示:
<ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/vector_image"/>
在代碼中設置的話,代碼以下所示:
ImageView iv = (ImageView) findViewById(R.id.iv); iv.setImageResource(R.drawable.vector_image);
setBackgroundResource也是能夠設置Vector的API
Button並不能直接使用app:srcCompat來使用Vector圖像,須要經過Selector來進行使用,首先,建立兩個圖像,用於Selector的兩個狀態,代碼以下所示:
selector1.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0"> <path android:fillColor="#FF000000" android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> </vector>
selector2.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0"> <path android:fillColor="#FF000000" android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/> </vector>
selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/selector1" android:state_pressed="true"/> <item android:drawable="@drawable/selector2"/> </selector>
很是簡單,只是把普通的Selector中的圖像換成了Vector圖像而已,接下來,在Button中使用這個Selector便可:
<Button android:id="@+id/btn" android:layout_width="70dp" android:layout_height="70dp" android:background="@drawable/selector"/>
而後運行,若是你認爲能夠運行,那就是太天真了,都說了是兼容,怎麼能沒有坑呢,這裏就是一個坑……
這個坑其實是有歷史淵源的,Google的一位開發者在博客中寫到:
First up, this functionality was originally released in 23.2.0, but then we found some memory usage and Configuration updating issues so we it removed in 23.3.0. In 23.4.0 (technically a fix release) we’ve re-added the same functionality but behind a flag which you need to manually enable.
實際上,他們的這個改動,就影響了相似DrawableContainers(DrawableContainers which reference other drawables resources which contain only a vector resource)這樣的類,它的一個典型,就是Selector(StateListDrawable也是)。這個開發者在文中提到的flag,就是下面的這段代碼,放在Activity的前面就能夠了:
static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); }
開啓這個flag後,你就能夠正常使用Selector這樣的DrawableContainers了。同時,你還開啓了相似android:drawableLeft這樣的compound drawable的使用權限,以及RadioButton的使用權限,以及ImageView’s src屬性。
RadioButton的Button一樣能夠定義,代碼以下所示:
<RadioButton android:layout_width="50dp" android:layout_height="50dp" android:button="@drawable/selector"/>
動態Vector纔是Android Vector Drawable的精髓所在
動態的Vector須要經過animated-vector標籤來進行實現,它就像一個粘合劑,將控件與Vector圖像粘合在了一塊兒,一個基礎的animated-vector代碼以下所示:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/XXXXX1"> <target android:name="left" android:animation="@animator/XXXXX2"/> </animated-vector>
實際上這裏面只有兩個重點是須要關注的,XXXXX1和XXXXX2。一個具體的示例以下所示:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_arrow"> <target android:name="left" android:animation="@animator/anim_left"/> <target android:name="right" android:animation="@animator/anim_right"/> </animated-vector>
這裏表示目標圖像是drawable/ic_arrow,對left、right分別使用了anim_left、anim_right動畫。這裏的name屬性,就是在靜態Vector圖像中group或者path標籤的name屬性。
animated-vector標籤在如今的Android Studio中其實是會報錯的,但這個並不影響編譯和運行,屬於Android Studio的Bug。
XXXXX1是目標Vector圖像,也就是靜態的Vector圖像,例如:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="120dp" android:height="120dp" android:viewportHeight="24.0" android:viewportWidth="24.0"> <group android:name="left"> <path android:fillColor="#FF000000" android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3"/> </group> <group android:name="right"> <path android:fillColor="#FF000000" android:pathData="M14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4"/> </group> </vector>
能夠發現,這裏的Vector圖像比以前咱們看見的要多了一個group標籤。group標籤的做用有兩個:
XXXXX2實際上就是模板要實現的動畫,動畫效果實際上就是基礎的屬性動畫,例如:
anim_left.xml
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:interpolator="@android:interpolator/anticipate_overshoot" android:propertyName="translateX" android:repeatCount="infinite" android:repeatMode="reverse" android:valueFrom="0" android:valueTo="-10" android:valueType="floatType"/>
anim_right.xml
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:interpolator="@android:interpolator/anticipate_overshoot" android:propertyName="translateX" android:repeatCount="infinite" android:repeatMode="reverse" android:valueFrom="0" android:valueTo="10" android:valueType="floatType"/>
ImageView imageView = (ImageView) findViewById(R.id.iv); AnimatedVectorDrawableCompat animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create( this, R.drawable.square_anim ); imageView.setImageDrawable(animatedVectorDrawableCompat); ((Animatable) imageView.getDrawable()).start();
一說到兼容,就不得不提到坑,幾乎全部的爲了兼容而作的改動,都會留下一些不可填滿的坑,動態Vector動畫也不例外,雖然Google已經對Vector圖像進行了Android 2.1以上的兼容,但對於動態Vector動畫,仍是有不少限制的,例如:
除了在低版本上的兼容性問題,在L版本以上,也存在兼容性問題,即繼承了AppCompatActivity的界面,若是直接設置ImageView的srcCompat,那麼Path Morphing動畫是沒法生效的,由於默認的AppCompatActivity已經默認使用ImageViewCompat給轉換了,可是AnimatedVectorDrawableCompat是不支持Path Morphing動畫的,因此,在AppCompatActivity界面裏面就無效了。
解決辦法很簡單,即便用代碼來給ImageView添加動畫:
ImageView imageView = (ImageView) view;
AnimatedVectorDrawable morphing = (AnimatedVectorDrawable) getDrawable(morphing);
imageView.setImageDrawable(morphing);
if (morphing != null) { morphing.start(); }
注意不要使用AnimatedVectorDrawableCompat便可。
開發者有時候爲了代碼簡潔可能會把Vector圖像中的pathData放到string.xml中,而後在Vector圖像中引用string。
但這種方式若是經過生成png來兼容5.0如下機型的話,會報pathData錯誤,編譯器不會去讀取string.xml,只能把pathData寫到Vector圖像中,動畫文件中也是同樣,這也是爲了兼容作出的犧牲嗎,不得而知。
其它很是奇怪、詭異、不能理解的兼容性問題,只能經過版本文件夾的方式來進行兼容了,例如drawable-v21和drawable,分別建立兩個文件名相同的資源在兩個文件夾下,這樣在21以上版本,會使用drawable-v21的資源,而其它會使用drawable下的資源。
所謂Vector動畫進階,實際上就是在利用ObjectAnimator的一些屬性,特別是trimPathStart、trimPathEnd這兩個針對Vector的屬性(要注意pathData屬性不兼容pre-L)。
這兩個屬性的官方文檔以下所示:
android:trimPathStart
The fraction of the path to trim from the start, in the range from 0 to 1. android:trimPathEnd The fraction of the path to trim from the end, in the range from 0 to 1. android:trimPathOffset Shift trim region (allows showed region to include the start and end), in the range from 0 to 1.
其實很簡單,就是一個圖像的截取,設置一個比例便可,即當前繪製多少比例的圖像,其他部分不繪製,Start和End分別就是從PathData的Start和End開始算,你們參考幾個例子就能理解了。
Path Morph動畫是Vector動畫的一個高級使用,說到底,也就是兩個PathData的轉換,可是這種轉換並非爲所欲爲的,對於兩個PathData,它們能進行Path Morph的前提是,它們具備相同個數的關鍵點,即兩個路徑的變換,只是關鍵點的座標變化,掌握了這一個基本原理,實現Path Morph就很是容易了。
在Github上我開源了一個Vector的動畫Demo庫,地址以下所示:
https://github.com/xuyisheng/VectorDemo
這個Demo分爲兩部分,一部分是能夠兼容Android pre-L版本和L+版本的Vector動畫,另外一部分(經過Actionbar的按鈕切換)是隻能兼容L+的Vector動畫。
每一個Vector動畫,基本都包含四部份內容,即:
每一個Vector動畫經過這四個部分去進行分析,就很是清晰了。
這裏展現下Demo的效果圖:
有讀者在文章後面留言,詢問VectorDrawable的性能問題,這裏解釋一下。
Google的這個視頻中,已經對Vector的效率問題作了解釋,能夠參考下:
https://www.youtube.com/watch?v=wlFVIIstKmA&feature=youtu.be&t=6m3s
https://medium.com/@shemag8/animated-vector-drawable-e4d7743d372c#.3vkt12j20
https://github.com/jpuderer/AnimatedButton