天天看點

Android Vector曲折的相容之路Android Vector曲折的相容之路

兩年前寫書的時候,就在研究Android L提出的Vector,可研究下來發現,完全不具備相容性,相信這也是它沒有被廣泛使用的一個原因,經過Google的不懈努力,現在Vector終于迎來了它的春天。

Android Vector曲折的相容之路Android Vector曲折的相容之路

Android 5.0釋出的時候,Google提供了Vector的支援。Vector Drawable相對于普通的Drawable來說,有以下幾個好處:

Vector圖像可以自動進行适配,不需要通過分辨率來設定不同的圖檔

Vector圖像可以大幅減少圖像的體積,同樣一張圖,用Vector來實作,可能隻有PNG的幾十分之一

使用簡單,很多設計工具,都可以直接導出SVG圖像,進而轉換成Vector圖像

功能強大,不用寫很多代碼就可以實作非常複雜的動畫

成熟、穩定,前端已經非常廣泛的進行使用了

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應該算是迎來了它的春天。

首先,需要講解兩個概念——SVG和Vector。

Android以一種簡化的方式對SVG進行了相容,這種方式就是通過使用它的Path标簽,通過Path标簽,幾乎可以實作SVG中的其它所有标簽,雖然可能會複雜一點,但這些東西都是可以通過工具來完成的,是以,不用擔心寫起來會很複雜。

Path指令解析如下所示:

支援的指令:

M = moveto(M X,Y) :将畫筆移動到指定的坐标位置

L = lineto(L X,Y) :畫直線到指定的坐标位置

H = horizontal lineto(H X):畫水準線到指定的X坐标位置

V = vertical lineto(V Y):畫垂直線到指定的Y坐标位置

C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次貝賽曲線

S = smooth curveto(S X2,Y2,ENDX,ENDY)

Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次貝賽曲線

T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射

A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧線

Z = closepath():關閉路徑

使用原則:

坐标軸為以(0,0)為中心,X軸水準向右,Y軸水準向下

所有指令大小寫均可。大寫絕對定位,參照全局坐标系;小寫相對定位,參照父容器坐标系

指令和資料間的空格可以省略

同一指令出現多次可以隻用一個

注意,’M’處理時,隻是移動了畫筆, 沒有畫任何東西。 它也可以在後面給出上同時繪制不連續線。

關于這些文法,開發者需要的并不是全部精通,而是能夠看懂即可,其它的都可以交給工具來實作。

設計師

要從一般使用的PNG圖像轉換到SVG圖像,對于設計師來說,并不是一件難事,因為大部分的設計工具(PS、Illustrator等等)都支援導出各種格式的圖像,如PNG、JPG,當然,也包括SVG,是以,設計師可以完全按照原有的方式進行設計,隻是最後導出的時候,選擇SVG即可。

程式員

Android Vector曲折的相容之路Android Vector曲折的相容之路
Android Vector曲折的相容之路Android Vector曲折的相容之路

利用Android Studio的Vector Asset,可以非常友善的建立Vector圖像,甚至可以直接通過本地的SVG圖像來生成Vector圖像,如圖所示:

Android Vector曲折的相容之路Android Vector曲折的相容之路

進去之後,就可以生成Vector圖像,如圖所示:

Android Vector曲折的相容之路Android 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以上:

使用Gradle Plugin 2.0以下,Gradle Plugin 1.5以上:

像前面提到的,這種相容方式實際上是先關閉AAPT對pre-L版本使用Vector的妥協,即在L版本以上,使用Vector,而在pre-L版本上,使用Gradle生成相應的PNG圖檔,generatedDensities這個數組,實際上就是要生成PNG的圖檔分辨率的數組,使用appcompat後就不需要這樣了。

當然,最重要的還是添加appcompat的支援:

同時,確定你使用的是AppCompatActivity而不是普通的Activity。

一個基本的Vector圖像,實際上也是一個xml檔案,如下所示:

顯示如圖所示:

Android Vector曲折的相容之路Android Vector曲折的相容之路

這裡需要解釋下這裡的幾個标簽:

android:width \ android:height:定義圖檔的寬高

android:viewportHeight \ android:viewportWidth:定義圖像被劃分的比例大小,例如例子中的500,即把200dp大小的圖像劃分成500份,後面Path标簽中的坐标,就全部使用的是這裡劃分後的坐标系統。

這樣做有一個非常好的作用,就是将圖像大小與圖像分離,後面可以随意修改圖像大小,而不需要修改PathData中的坐标。

android:fillColor:PathData中的這些屬性就不詳細講了,與Canvas繪圖的屬性基本類似。

有了靜态的Vector圖像,就可以在控件中使用了。

可以發現,這裡我們使用的都是普通的ImageView,好像并不是AppcomatImageView,這是因為使用了Appcomat後,系統會自動把ImageView轉換為AppcomatImageView。

對于ImageView這樣的控件,要相容Vector圖像,隻需要将之前的android:src屬性,換成app:srcCompat即可,示例代碼如下所示:

在代碼中設定的話,代碼如下所示:

setBackgroundResource也是可以設定Vector的API

Button并不能直接使用app:srcCompat來使用Vector圖像,需要通過Selector來進行使用,首先,建立兩個圖像,用于Selector的兩個狀态,代碼如下所示:

selector1.xml

selector2.xml

selector.xml

非常簡單,隻是把普通的Selector中的圖像換成了Vector圖像而已,接下來,在Button中使用這個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的前面就可以了:

開啟這個flag後,你就可以正常使用Selector這樣的DrawableContainers了。同時,你還開啟了類似android:drawableLeft這樣的compound drawable的使用權限,以及RadioButton的使用權限,以及ImageView’s src屬性。

RadioButton的Button同樣可以定義,代碼如下所示:

動态Vector才是Android Vector Drawable的精髓所在

動态的Vector需要通過animated-vector标簽來進行實作,它就像一個粘合劑,将控件與Vector圖像粘合在了一起,一個基礎的animated-vector代碼如下所示:

實際上這裡面隻有兩個重點是需要關注的,XXXXX1和XXXXX2。一個具體的示例如下所示:

這裡表示目标圖像是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圖像比之前我們看見的要多了一個group标簽。group标簽的作用有兩個:

對Path進行分組,由于我們後面需要針對Path進行動畫,是以可以讓具有同樣動畫效果的Path在同一個Group中

拓展動畫效果,單個的path标簽是沒有translateX和translateY屬性的,是以無法使用屬性動畫來控制path translateY,而group标簽是有的,是以我們需要先将相關的path标簽元素包裹在一個個的group标簽中.

XXXXX2實際上就是模闆要實作的動畫,動畫效果實際上就是基礎的屬性動畫,例如:

anim_left.xml

anim_right.xml

一說到相容,就不得不提到坑,幾乎所有的為了相容而做的改動,都會留下一些不可填滿的坑,動态Vector動畫也不例外,雖然Google已經對Vector圖像進行了Android 2.1以上的相容,但對于動态Vector動畫,還是有很多限制的,例如:

Path Morphing,即路徑變換動畫,在Android pre-L版本下是無法使用的。

Path Interpolation,即路徑插值器,在Android pre-L版本隻能使用系統的插值器,不能自定義。

Path Animation,即路徑動畫,這個一般使用貝塞爾曲線來代替,是以沒有太大影響。

除了在低版本上的相容性問題,在L版本以上,也存在相容性問題,即繼承了AppCompatActivity的界面,如果直接設定ImageView的srcCompat,那麼Path Morphing動畫是無法生效的,因為預設的AppCompatActivity已經預設使用ImageViewCompat給轉換了,但是AnimatedVectorDrawableCompat是不支援Path Morphing動畫的,是以,在AppCompatActivity界面裡面就無效了。

解決辦法很簡單,即使用代碼來給ImageView添加動畫:

注意不要使用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)。

這兩個屬性的官方文檔如下所示:

其實很簡單,就是一個圖像的截取,設定一個比例即可,即目前繪制多少比例的圖像,其餘部分不繪制,Start和End分别就是從PathData的Start和End開始算,大家參考幾個例子就能了解了。

Path Morph動畫是Vector動畫的一個進階使用,說到底,也就是兩個PathData的轉換,但是這種轉換并不是随心所欲的,對于兩個PathData,它們能進行Path Morph的前提是,它們具有相同個數的關鍵點,即兩個路徑的變換,隻是關鍵點的坐标變化,掌握了這一個基本原理,實作Path Morph就非常容易了。

在Github上我開源了一個Vector的動畫Demo庫,位址如下所示:

<a href="https://github.com/xuyisheng/VectorDemo">https://github.com/xuyisheng/VectorDemo</a>

這個Demo分為兩部分,一部分是可以相容Android pre-L版本和L+版本的Vector動畫,另一部分(通過Actionbar的按鈕切換)是隻能相容L+的Vector動畫。

每個Vector動畫,基本都包含四部分内容,即:

Vector:圖像資源

Animated-vector:動畫、圖像粘合劑

ObjectAnimator:動畫資源

代碼:啟動動畫

每個Vector動畫通過這四個部分去進行分析,就非常清晰了。

這裡展示下Demo的效果圖:

Android Vector曲折的相容之路Android Vector曲折的相容之路

有讀者在文章後面留言,詢問VectorDrawable的性能問題,這裡解釋一下。

Bitmap的繪制效率并不一定會比Vector高,它們有一定的平衡點,當Vector比較簡單時,其效率是一定比Bitmap高的,是以,為了保證Vector的高效率,Vector需要更加簡單,PathData更加标準、精簡,當Vector圖像變得非常複雜時,就需要使用Bitmap來代替了

Vector适用于ICON、Button、ImageView的圖示等小的ICON,或者是需要的動畫效果,由于Bitmap在GPU中有緩存功能,而Vector并沒有,是以Vector圖像不能做頻繁的重繪

Vector圖像過于複雜時,不僅僅要注意繪制效率,初始化效率也是需要考慮的重要因素

SVG加載速度會快于PNG,但渲染速度會慢于PNG,畢竟PNG有硬體加速,但平均下來,加載速度的提升彌補了繪制的速度缺陷。

Google的這個視訊中,已經對Vector的效率問題做了解釋,可以參考下:

<a href="https://www.youtube.com/watch?v=wlFVIIstKmA&amp;feature=youtu.be&amp;t=6m3s">https://www.youtube.com/watch?v=wlFVIIstKmA&amp;feature=youtu.be&amp;t=6m3s</a>

<a href="https://medium.com/@shemag8/animated-vector-drawable-e4d7743d372c#.3vkt12j20">https://medium.com/@shemag8/animated-vector-drawable-e4d7743d372c#.3vkt12j20</a>

<a href="https://github.com/jpuderer/AnimatedButton">https://github.com/jpuderer/AnimatedButton</a>

繼續閱讀