SVG和Vector的概念和如何在Android Studio中使用

????今天我在看某腦SVG視頻和網上查資料時,發現了和某位大佬的寫文章的某種巧合(報以微妙的笑容)。因為強迫癥,所以我想總結性的抄襲一下。不論是否被發現,一切榮譽歸屬于大佬。
????抄襲對象大佬: Android Vector曲折的兼容之路

引導:

Android微信上的SVG

概念

SVG

????即Scalable Vector Graphics 可伸縮矢量圖形,這種圖像格式在前端中已經使用的非常廣泛了。詳見WIKI:https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
????什么是矢量圖像,什么是位圖圖像?

  • 矢量圖像:SVG是W3C 推出的一種開放標準的文本式矢量圖形描述語言,他是基于XML的、專門為網絡而設計的圖像格式,SVG是一種采用XML來描述二維圖形的語言,所以它可以直接打開xml文件來修改和編輯。
  • 位圖圖像:位圖圖像的存儲單位是圖像上每一點的像素值,因而文件會比較大,像GIF、JPEG、PNG等都是位圖圖像格式。
    ????在android開發中,不能直接使用SVG文件作為圖片文件,否則就會這樣:


    不能直接復制svg文件到as里

    ????這個時候,我們就需要用到Vector。

Vector

????在Android中指的是Vector Drawable,也就是Android中的矢量圖,可以說Vector就是Android中的SVG實現(并不是支持全部的SVG語法,現已支持的完全足夠用了)詳見:https://developer.android.com/reference/android/graphics/drawable/VectorDrawable.html
????補充:Vector圖像剛發布的時候,是只支持Android 5.0+的,但是在之后,谷歌進行了兼容:
1. Gradle Plugin 1.5的兼容
????從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圖片的密度。
2. AppCompat23.2的兼容
????從com.android.support:appcompat-v7的版本為23.2.0起,Vector可以兼容到Android 2.1以上的所有系統,只需要

 compile 'com.android.support:appcompat-v7:23.2.0'//及以上

????23.2.0及以上的版本就可以了。
????注意事項:所謂的兼容并非能夠使用SVG了,本質還是生成了PNG圖片來使用。

不同版本的本質

Vector Drawable:

????Android 5.0發布的時候,Google提供了Vector的支持,即:Vector Drawable類。
????Vector Drawable相對于普通的Drawable來說,有以下幾個好處:

  • Vector圖像可以自動進行適配,不需要通過分辨率來設置不同的圖片,這在我眼里是最好的地方。
  • Vector圖像可以大幅減少圖像的體積,同樣一張圖,用Vector來實現,可能只有PNG的幾十分之一,這從減少apk大小上很有意義,但有大佬發現,其實并不是完全就能減少,當PNG只有十幾k,用Vector反而變大了。
  • 使用簡單,很多設計工具如PhotoShop、Illustrator,都可以直接導出SVG圖像,從而轉換成Vector圖像,夠靈活,不用寫很多代碼就可以實現非常復雜的動畫。
  • 成熟、穩定,前端已經非常廣泛的進行使用了。

Vector 語法簡介

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

  • Path指令解析如下所示:
M = moveto(M X,Y) :將畫筆移動到指定的坐標位置,相當于 android Path 里的moveTo()
L = lineto(L X,Y) :畫直線到指定的坐標位置,相當于 android Path 里的lineTo()
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):弧線 ,相當于arcTo()
Z = closepath():關閉路徑(會自動繪制鏈接起點和終點)
  • 使用原則:
  1. 坐標軸為以(0,0)為中心,X軸水平向右,Y軸水平向下
  2. 所有指令大小寫均可。大寫絕對定位,參照全局坐標系;小寫相對定位,參照父容器坐標系
  3. 指令和數據間的空格可以省略
  4. 同一指令出現多次可以只用一個

????注意事項

  1. ’M’處理時,只是移動了畫筆, 沒有畫任何東西。
  2. 關于這些語法,開發者不需要全部精通,而是能夠看懂即可,這些path標簽及數據生成都可以交給美工工具和網上的網站來實現:
    1)可以使用SVG的編輯器來進行SVG圖像的編寫,例如:http://editor.method.ac/
    SVG圖像的編寫

    2)有些網站可以找到SVG資源
    SVG下載地址: http://www.shejidaren.com/8000-flat-icons.html
    http://www.flaticon.com/
    阿里的iconfont :http://www.iconfont.cn/home/index?spm=a313x.7781069.1998910419.1
    我個人喜歡后面那個。
    3)對SVG文件用http://inloop.github.io/svg2android/ 生成 VectorDrawable xml代碼
    上傳圖片文件到網站里

    自動生成Vector的xml文件

    4)使用Android Studio自帶功能Vector Asset Studio,完成SVG添加,將SVG文件轉成Vector。
    阿里的iconfont下載svg文件1

    阿里的iconfont下載svg文件2

    image.png

    阿里的iconfont下載的svg文件

    as里選擇Vector Asset

    選擇生成可用文件方式

    選擇生成可用文件所在位置

    生成成功,查看效果

    補充:也可參考網址http://www.lxweimin.com/p/d6c39f2dd5e7

靜態Vector圖像

  1. 生成圖片
    ????例如: 我們用as生成一個矩形
<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>
矩形

????解釋頭部的幾個標簽:
????android:width /android:height:定義圖片的寬高
????android:viewportHeight / android:viewportWidth:定義圖像被劃分的比例大小,例如例子中的500,即把200dp大小的圖像劃分成500份,后面Path標簽中的坐標,就全部使用的是這里劃分后的坐標系統。
????這樣做有一個非常好的作用,就是將圖像大小與圖像分離,后面可以隨意修改圖像大小,而不需要修改PathData中的坐標。

  1. 在控件中使用
  • 使用ImageView,就當普通的圖片使用就可以了。
            <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);
                iv.setBackgroundResource(R.drawable.vector_image);//setBackgroundResource也是可以設置Vector的API

????注意事項

  1. 當我的minSdkVersion 小于21時,不在5.0+,則不能對Vector直接使用app:srcCompat,否則會報錯,需要去build.gradle里去設置一下。后面會在兼容里講【1】。如果是minSdkVersion>=21,當然是不會報錯的了。


    需build里設置
  2. 使用的都是普通的ImageView,而沒有改成AppcomatImageView,這是因為使用了Appcomat后,系統會自動把ImageView轉換為AppcomatImageView。
  • 如果是Button,則不能直接使用app:srcCompat來直接使用Vector圖像,需要通過Selector來進行使用,首先,創建兩個圖像,用于Selector的兩個狀態(寫兩個SVG的Drawable):
                <?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"/>

????然后運行時又會發現有個兼容的坑,也在下面講【2】。

動態Vector

????動態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>

????舉例:


一開始為勾,后為叉的效果
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                 android:drawable="@drawable/path_tick" >
    <target
        android:name="tickCrossGroup"
        android:animation="@animator/anim_path_tick2cross" />
</animated-vector>

????設置了初始顯示時的“path_tick”Vector圖像,然后,target目標是最終達到“anim_path_tick2cross”效果。
????注意事項

animated-vector注意事項

????animated-vector在Android Studio中實際上是會報錯的,但這個并不影響編譯和運行,屬于Android Studio的Bug。
path_tick

????path_tick代碼為:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="120dp"
        android:width="120dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
    <group>
        <path
            android:name="tickCrossGroup"
            android:pathData="M19.6,7 L10.4,16.2 M4.8,13.4 L9,17.6"
            android:strokeColor="#000"
            android:strokeWidth="2"/>
    </group>
</vector>

????path_tick是目標Vector圖像,也就是靜態的Vector圖像。
????可以發現,這里的Vector圖像有個group標簽。group標簽的作用有兩個:

  • 對Path進行分組,由于我們后面需要針對Path進行動畫,所以可以讓具有同樣動畫效果的Path在同一個Group中。
  • 拓展動畫效果,單個的path標簽是沒有translateX和translateY屬性的,因此無法使用屬性動畫來控制path translateY,而group標簽是有的,所以我們需要先將相關的path標簽元素包裹在一個個的group標簽中。
    ????anim_path_tick2cross代碼為為:
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="300"
        android:propertyName="pathData"
        android:valueFrom="M19.6,7 L10.4,16.2 M4.8,13.4 L9,17.6"
        android:valueTo="M17.6,6.4 L6.4,17.6 M6.4,6.4 L17.6,17.6"
        android:valueType="pathType"/>
</set>

????anim_path_tick2cross實際上就是利用屬性動畫來實現的動畫效果。
????android:propertyName屬性名,上文里為pathData,指的是path_tick里的

 android:pathData="M19.6,7 L10.4,16.2 M4.8,13.4 L9,17.6"

????從 android:valueFrom動畫效果到 android:valueTo。
????當然,也可以android:propertyName="translationX"這樣換別的動畫屬性。
寫好了動態Vector,作為動畫,肯定是要開啟執行動畫。怎么搞?

  <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="anim"
            app:srcCompat="@drawable/path_tick2cross_anim"/>
 public void anim(View view) {
ImageView imageView = (ImageView) view;
AnimatedVectorDrawableCompat animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(this, R.drawable.path_tick2cross_anim);
imageView.setImageDrawable(animatedVectorDrawableCompat);
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof Animatable) {
            ((Animatable) drawable).start();
        }
    }

????判斷imageView的圖片是個動圖,即屬于Animatable,則強轉并啟動動畫。這樣不再需要動態寫代碼動畫來搞了,向美工御姐撒撒嬌就好了。
????注意事項 :如果是兩個SVG進行動畫,path元素中從一個形狀轉變到另一個形狀,這兩個形狀必須滿足:要有一致的命令(command)個數(逗號分割開的為命令),并且每個命令的參數個數也必須一致。

版本兼容

????VectorDrawableCompat依賴于AAPT的一些功能,它能保持最近矢量圖使用的添加的屬性ID,以便他們可以在L版本之前被引用。
????在Android 5.0之前使用Vector,需要aapt來對資源進行一些處理,這一過程可以在aapt的配置中進行設置,如果沒有啟用這樣一個flag,那么在5.0以下的設備上運行就會發生android.content.res.Resources$NotFoundException。

  1. 最重要的是添加appcompat的支持,如:
compile 'com.android.support:appcompat-v7:25.3.1'

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

  1. 你需要在項目的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,而在L版本前,使用Gradle生成相應的PNG圖片,generatedDensities這個數組,實際上就是要生成PNG的圖片分辨率的數組,使用appcompat后就不需要這樣了。


L版本的前后

????這就是解決了【1】的問題。

  1. 版本不同,對DrawableContainers兼容性問題
    ????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的src屬性。
????這就是解決了【2】的問題。

  1. 動態Vector動畫兼容性問題
    ????1) 向下兼容問題
    ????一說到兼容,就不得不提到坑,幾乎所有的為了兼容而做的改動,都會留下一些不可填滿的坑,動態Vector動畫也不例外,雖然Google已經對Vector圖像進行了Android 2.1以上的兼容,但對于動態Vector動畫,還是有很多限制的,例如:
    ????Path Morphing,即路徑變換動畫,在Android pre-L版本下是無法使用的。
    ???? Path Interpolation,即路徑插值器,在Android pre-L版本只能使用系統的插值器,不能自定義。
    ????Path Animation,即路徑動畫,這個一般使用貝塞爾曲線來代替,所以沒有太大影響。
    ????2) 向上兼容問題
    ????除了在低版本上的兼容性問題,在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即可。

  1. 抽取string兼容問題
    ????開發者有時候為了代碼簡潔可能會把Vector圖像中的pathData放到string.xml中,然后在Vector圖像中引用string。
    ????但這種方式如果通過生成png來兼容5.0以下機型的話,會報pathData錯誤,編譯器不會去讀取string.xml,只能把pathData寫到Vector圖像中,動畫文件中也是一樣。
  2. 其它兼容問題
    ????其它非常奇怪、詭異、不能理解的兼容性問題,只能通過版本文件夾的方式來進行兼容了,例如drawable-v21和drawable,分別創建兩個文件名相同的資源在兩個文件夾下,這樣在21以上版本,會使用drawable-v21的資源,而其它會使用drawable下的資源。
  3. 即時有了兼容方式,但是,還是會有一些Vector動畫是不能兼容Android pre-L版本的,只能兼容L+。

學習Vector

????大佬的Vector動畫Demo庫,地址如下所示:
https://github.com/xuyisheng/VectorDemo
????這個Demo分為兩部分,一部分是可以兼容Android pre-L版本和L+版本的Vector動畫,另一部分(通過Actionbar的按鈕切換)是只能兼容L+的Vector動畫。
????注意事項:這個能兼容和不能兼容,是因為Vector動畫本身的效果不能在pre-L版本兼容。不是代碼沒設置。
????每個Vector動畫,基本都包含四部分內容,即:

Vector:圖像資源
Animated-vector:動畫、圖像粘合劑
ObjectAnimator:動畫資源
代碼:啟動動畫

每個Vector動畫通過這四個部分去進行分析,就非常清晰了。
這里展示下Demo的效果圖:


Demo

VectorDrawable的性能問題:

  • Bitmap的繪制效率并不一定會比Vector高,它們有一定的平衡點,當Vector比較簡單時,其效率是一定比Bitmap高的,所以,為了保證Vector的高效率,Vector需要更加簡單,PathData更加標準、精簡,當Vector圖像變得非常復雜時,就需要使用Bitmap來代替了
  • Vector適用于ICON、Button、ImageView的圖標等小的ICON,或者是需要的動畫效果,由于Bitmap在GPU中有緩存功能,而Vector并沒有,所以Vector圖像不能做頻繁的重繪
  • 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
Android vector標簽 PathData 畫圖超詳解
Android Vector曲折的兼容之路

有個加急項目來了,所以留待以后再更進一步研究。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容