Android矢量圖(一)--VectorDrawable基礎(chǔ)

背景

維基百科中的定義:

可縮放向量圖形Scalable Vector Graphics,SVG)是一種基于可擴(kuò)展標(biāo)記語(yǔ)言(XML),用于描述二維向量圖形的圖形格式。SVG由W3C制定,是一個(gè)開放標(biāo)準(zhǔn)。

1,SVG何以可以任意縮放而不會(huì)失真,drawable-(m|h|xh|xxh|xxxh)dpi和mipmap-(m|h|xh|xxh|xxxh)dpi這倆貨就可以省省了;2,SVG文件一般都比較小,省去很去資源達(dá)到apk縮包的目的;3,SVG占用內(nèi)存非常小,性能高。但是SVG明顯的缺點(diǎn)是沒有位圖表達(dá)的色彩豐富。

Android API 21(5.0)引入了一個(gè)Drawable的子類VectorDrawable目的就是用來(lái)渲染矢量圖,AnimatedVectorDrawable用來(lái)播放矢量動(dòng)畫。之前老的小于21的API設(shè)備可以分別使用VectorDrawableCompatAnimatedVectorDrawableCompat這兩個(gè)兼容包來(lái)同樣達(dá)到渲染矢量圖的目的。本文只討論矢量圖,不討論矢量動(dòng)畫。

準(zhǔn)備

使用矢量圖要根據(jù)minSdkVersion來(lái)分3中不同的情況:

  1. minSdkVersion>=21:用xml文件或者代碼定義VectorDrawable,和普通的Drawable用法一樣,不再需要額外任何東西;如何編寫矢量圖,下文有介紹;
  2. minSdkVersion<21:如果想要渲染矢量圖的話必須在app模塊的build.gralde文件里添加一行代碼:
defaultConfig {
    vectorDrawables.useSupportLibrary = true
}
  1. minSdkVersion<21以及更多:上面的第二種情況是使用兼容包,但是兼容包僅支持AppCompatImageView和AppCompatImageButton及其子類矢量圖,而且矢量圖的引用必須放在app:srcCompat屬性中才會(huì)被識(shí)別并生效,代碼必須這樣寫才行:
<android.support.v7.widget.AppCompatImageView
      app:layout_constraintBottom_toBottomOf="parent"
      android:layout_width="100dp"
      android:layout_height="100dp"
      app:srcCompat="@drawable/ic_oval"/>

ic_oval.xml是我們使用xml編寫的矢量圖,如果想要TextView的drawableTop或者其他額外方式使用矢量圖渲染,那么必須在Activity中加入代碼:

static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

同時(shí)這個(gè)Activity必須繼承AppCompatActivity這個(gè)compat兼容包屬性才會(huì)生效。

minSdkVersion<21情況下在非app:srcCompat屬性的地方使用矢量圖時(shí),需要將矢量圖用drawable容器(如StateListDrawable, InsetDrawable, LayerDrawable, LevelListDrawable, 和RotateDrawable)包裹起來(lái)使用。否則會(huì)在低版本的情況下報(bào)錯(cuò)org.xmlpull.v1.XmlPullParserException: Binary XML file line #0: invalid drawable tag vector。minSdkVersion>=21則沒有任何限制。

矢量圖使用

準(zhǔn)備工作做好之后,我們就需要自己動(dòng)手編輯矢量圖了。VectorDrawable類在xml中對(duì)應(yīng)的是標(biāo)簽是vector。我目前所知道的是只有xml文件才能決定矢量圖的樣子(也就是編輯pathData、fillColor等屬性),貌似無(wú)法使用代碼來(lái)決定矢量圖的繪制邏輯,而只能使用代碼加載編輯好的xml文件,這個(gè)xml文件有兩種方法來(lái)創(chuàng)建:

  1. 右擊drawable-->Drawable resource file-->設(shè)置root element為vector,這樣的矢量圖繪制邏輯完全掌握在開發(fā)者手里;
  2. 右擊drawable-->Vector Asset,選擇SVG或者PSD文件直接生成根標(biāo)簽為vector的xml文件,可以百度或者Google怎樣把png轉(zhuǎn)換成SVG。

寫了這么多字,一直在瞎扯淡而沒談重點(diǎn),下面我們看下根標(biāo)簽為vector的xml文件的真面目,代碼:


圖1

上圖中標(biāo)簽vector使用了四個(gè)屬性:android:width="24dp"、android:height="24dp"、android:viewportHeight="300.0"、android:viewportWidth="300.0"。

  1. width和height:當(dāng)使用這個(gè)矢量圖的View的寬高是wrap_content 的時(shí)候這兩個(gè)屬性才生效;
  2. viewportWidth和viewportHeight:決定畫布的寬高,是定義的一個(gè)虛擬空間,方便編輯pathData屬性,如果pathData中的點(diǎn)超出了這個(gè)虛擬空間,超出的部分將不會(huì)展現(xiàn)給用戶;虛擬空間的原點(diǎn)仍然還是在左上角(R點(diǎn)就是原點(diǎn))。

path標(biāo)簽是vector標(biāo)簽的子標(biāo)簽,它使用了以下屬性:

  1. android:name:類似View的id屬性,方便path被引用,如上圖的edge是虛擬空間四個(gè)邊界的path,oval是一個(gè)橢圓的path;
  2. android:fillColor:填充path的顏色,如果沒有定義則不填充path
  3. android:strokeColor:path邊框顏色,如果沒有定義則不顯示邊框
  4. android:strokeWidth:path邊框的粗細(xì)尺寸
  5. android:pathData:path指令,決定path的移動(dòng)和繪制邏輯,這個(gè)是最主要的屬性,下面詳細(xì)討論。

更多path屬性請(qǐng)參考鏈接。

pathData的指令和Path類的API方法基本差不多,比如M指令對(duì)應(yīng)moveTo方法,m指令對(duì)應(yīng)rMoveTo方法,下面是一些基本的指令:

    1. Mx,y:移動(dòng)到點(diǎn)(x,y)
    1. Lx,y:直線連到點(diǎn)x,y,簡(jiǎn)化命令H(x)水平連接和V(y)垂直連接;
    1. Qx1,y1 x2,y2:二階貝塞爾曲線,控制點(diǎn)(x1,y1),終點(diǎn)x2,y2;
    1. Cx1,y1 x2,y2 x3,y3:三階貝塞爾曲線,控制點(diǎn)(x1,y1)( x2,y2),終點(diǎn)x3,y3;
    1. Tx y:平滑的二階貝塞爾曲線,參數(shù)只有一個(gè)點(diǎn)(x,y),這個(gè)點(diǎn)是結(jié)束點(diǎn),控制點(diǎn)是前一個(gè)二階貝塞爾曲線的控制點(diǎn)相對(duì)于前一個(gè)貝塞爾曲線的結(jié)束點(diǎn)的鏡像點(diǎn)。
    1. Sx2,y2 x,y:平滑的三階貝塞爾曲線,參數(shù)為(x2,y2 x,y) ,x2,y2 為第二個(gè)控制點(diǎn),x,y為繪制終點(diǎn),那么第一個(gè)控制點(diǎn)則是前一個(gè)三階曲線的第二個(gè)控制點(diǎn)相對(duì)于前一個(gè)三階曲線終點(diǎn)的鏡像點(diǎn)。
    1. Arx,ry x-axis-rotation large-arc-flag,sweep-flag x,y:ellipse arc圓弧曲線
    1. z:close閉合
      ......

每個(gè)指令都有大小寫形式,大寫表示后面的參數(shù)是絕對(duì)坐標(biāo),小寫表示相對(duì)于上一個(gè)點(diǎn)的相對(duì)坐標(biāo)位置,參數(shù)可以用逗號(hào)或者空格分離。
只要掌握上面5個(gè)基本指令就能編輯pathData并且繪制一些酷炫的SVG。更詳細(xì)全面的path指令請(qǐng)參閱鏈接。

估計(jì)你已經(jīng)發(fā)現(xiàn)了,圓弧曲線指令A(yù)竟然那么多參數(shù),這直接嚇跑了很多的程序員,其實(shí)也并不難,且慢慢道來(lái)。

先根據(jù)圖1里的代碼來(lái)分析pathData指令。如圖一所示,edge這個(gè)path使用了四個(gè)相對(duì)指令,首先指令h300 0相對(duì)向右水平移動(dòng)300到點(diǎn)S,然后指令v0 300相對(duì)向下垂直移動(dòng)300到T,再次指令h-300 0相對(duì)向左水平移動(dòng)300到U,最后指令v0 -300相對(duì)向上垂直移動(dòng)300到起點(diǎn)R,這樣就根據(jù)屬性strokeColor和strokeWidth繪制了四條直線,最后一個(gè)指令可以使用z代替。這很簡(jiǎn)單吧?!

再來(lái)看oval這個(gè)path。它使用了三條指令。第一條指令移動(dòng)到點(diǎn)M處,第二條指令a75,75 0 1,1 150,0繪制M-N-O的弧線,第三條指令a75,75 0 1,1 -150,0繪制O-P-M的弧線。a指令共有7個(gè)參數(shù):rx和ry表示橢圓的兩個(gè)半徑,x-axis-rotation表示x軸的旋轉(zhuǎn)角度,x和y表示繪制橢圓弧線的終點(diǎn),這5個(gè)參數(shù)很簡(jiǎn)單很好理解,large-arc-flag和sweep-flag這兩個(gè)參數(shù)有點(diǎn)唬人。
解釋large-arc-flag和sweep-flag這兩個(gè)參數(shù)之前先考慮下這個(gè)題目:已知橢圓的半徑rx和ry,請(qǐng)繪制若干條從起始點(diǎn)A到終點(diǎn)B的橢圓弧線。題目中是若干條,那到底幾條?。恳话闱闆r下會(huì)有四條橢圓弧線(特殊情況是rx=線段AB的一半或者ry=線段AB的一半,這時(shí)候的橢圓弧線只有兩條),而large-arc-flag和sweep-flag這兩個(gè)參數(shù)就從這四個(gè)橢圓弧線中選取了最終的一條進(jìn)行繪制。large-arc-flag決定是大弧線還是小弧線,1大0小,sweep-flag決定是順時(shí)針弧線還是逆時(shí)針弧線,1順0逆。

圖2
??磮D2希望你能明白這兩個(gè)參數(shù)的意義。

有人可能會(huì)問(wèn),圖1的oval path是個(gè)圓,竟然使用了兩個(gè)a指令,使用一個(gè)a指令就能繪制圓的,只要終點(diǎn)回到起始點(diǎn)就能繪制圓的path了,剛開始我也是這樣認(rèn)為的,比如下面的代碼:

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="300.0"
    android:viewportWidth="300.0">
    <path
        android:name="circle"
        android:fillColor="@android:color/holo_green_light"
        android:pathData="
        M150,150
        a75,75 0 1,1 0,0"
        android:strokeColor="#00000000" />
</vector>

上面的代碼的path從起始點(diǎn)又回到了起始點(diǎn),不會(huì)繪制任何東西,終點(diǎn)x y需要和起始點(diǎn)錯(cuò)開幾個(gè)像素比如android:pathData="M150,150 a75,75 0 1,1 0,1"就大約是一個(gè)圓path,為什么說(shuō)是大約一個(gè)圓?因?yàn)槠鹗键c(diǎn)和終點(diǎn)不在一起,這只是一個(gè)圓的大弧線部分。推薦使用兩條a指令繪制圓path,因?yàn)橐粭la指令繪制的不是真正的圓path。

group標(biāo)簽

path沒有scale、rotatetranslate這三種屬性,因此也不能執(zhí)行這三種屬性動(dòng)畫,要達(dá)到這樣的目的需要借助group這個(gè)標(biāo)簽。group標(biāo)簽也是vector的一個(gè)子標(biāo)簽,它可以作為path或者其他group的父標(biāo)簽使用,將path和group組合成一個(gè)組來(lái)附加一些變換操作,這些變換操作包括scale、rotatetranslate共三種。這張圖3是來(lái)自android官網(wǎng)的vector標(biāo)簽樹型圖:

圖3
。<group>定義變換的細(xì)節(jié),<clip-path>定義裁剪區(qū)域。根據(jù)這三個(gè)變換操作,group標(biāo)簽有以下屬性:

  • android:name:group的名字;
  • android:rotation:group的旋轉(zhuǎn)角度,默認(rèn)0。
  • android:pivotX:scale和rotation變換中心點(diǎn)的X坐標(biāo),默認(rèn)0;
  • android:pivotY:scale和rotation變換中心點(diǎn)的Y坐標(biāo),默認(rèn)0;
  • android:scaleX:X軸方向的縮放,默認(rèn)1;
  • android:scaleY:Y軸方向的縮放,默認(rèn)1;
  • android:translateX:X軸方向的移動(dòng)距離,默認(rèn)0;
  • android:translateY:Y軸方向的移動(dòng)距離,默認(rèn)0。

這是group的全部屬性了,屬性都很簡(jiǎn)單,不需要解釋。

clip-path標(biāo)簽

<clip-path>定義當(dāng)前繪制的剪切路徑,就是圖像的一部分剪切下來(lái)。注意,clip-path只對(duì)當(dāng)前的vector和group以及當(dāng)前vector和group的孩子有效。這個(gè)標(biāo)簽僅有兩個(gè)屬性:

  • android:name:clip-path的名字;
  • android:pathData:clip-path的路徑。
<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="300.0"
    android:viewportWidth="300.0">
    <clip-path android:name="clip_one" android:pathData="
    M0 20a20 20 0 0 1 20 -20
    l260 0a20 20 0 0 1 20 20
    l0 260a20 20 0 0 1 -20 20
    l-260 0a20 20 0 0 1 -20 -20
    l0 -260"/>
    <path
        android:name="edge"
        android:pathData="h300v300h-300v-300
        M150 0 v300
        M0 150 h300"
        android:fillColor="@android:color/holo_green_light"
        android:strokeColor="@android:color/holo_red_dark"
        android:strokeWidth="1" />
    <group>
        <clip-path android:name="clip_two" android:pathData="M0 150h300v150h-300v-150"/>
        <path
            android:name="oval"
            android:strokeLineCap="round"
            android:strokeLineJoin="round"
            android:pathData="M20 20 l260,260M280 20 l-260,260h100"
            android:strokeColor="#000000"
            android:strokeWidth="15"/>
    </group>
</vector>

上面代碼定義了兩個(gè)clip-path,其效果如圖4所示。
圖4.gif

build.gradle中vectorDrawables.useSupportLibrary屬性

build.gradle中的vectorDrawables.useSupportLibrary默認(rèn)是false,不設(shè)置為true的話會(huì)有什么問(wèn)題嗎?討論這個(gè)問(wèn)題也需要根據(jù)minSdkVersion具體分析:

  1. minSdkVersion>=21:這么高的API根本就不需要兼容包,仍然可以渲染矢量圖;
  2. minSdkVersion<21:不再使用矢量圖兼容包,不能渲染矢量圖,但是有趣的是vector標(biāo)簽仍然可以使用,低版本的API完全把VectorDrawable當(dāng)作Drawable使用了,VectorDrawable的特性完全失效。原理是vector xml文件會(huì)生成對(duì)應(yīng)的png文件,使用png方式渲染圖片,和矢量圖沒有任何關(guān)系。值得注意的是生成的png圖片size很小而且會(huì)忽略vector標(biāo)簽的android:tint屬性(貌似只忽略這個(gè)屬性,我試過(guò)vector標(biāo)簽的android:alpha屬性在生成的png圖片中仍然有效,生成的png文件目錄是app/build/generated/res/pngs/debug,minSdkVersion>=21或者vectorDrawables.useSupportLibrary=true的話不會(huì)生成這些png圖片)。而且path標(biāo)簽的color相關(guān)的屬性不能引用colors.xml的值,android:strokeColor="@android:color/holo_red_dark"這樣寫的話會(huì)編譯失敗,提示錯(cuò)誤:Can't process attribute android:strokeColor="@android:color/holo_red_dark": references to other resources are not supported by build-time PNG generation,而只能寫原生的16進(jìn)制color值比如android:strokeColor="#234aac"

想看具體信息請(qǐng)查看這篇文章。

SVG實(shí)戰(zhàn)

我做的項(xiàng)目中一張撲克png資源大小2k左右,我試著用矢量圖畫這些撲克牌。代碼如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:width="400dp"
    android:height="550dp"
    android:viewportHeight="550"
    android:viewportWidth="400.0">
    <group android:name="poker_diamond_a">
        <path
            android:name="border"
            android:strokeWidth="7"
            android:strokeColor="#96999c"
            android:fillColor="@android:color/white"
            android:pathData="M5 25a20 20 0 0 1 20 -20
            h350a20 20 0 0 1 20 20v500a20 20 0 0 1 -20 20h-350a20 20 0 0 1 -20 -20v-500"/>
        <path android:name="a"
            android:strokeWidth="8"
            android:strokeColor="#cc0000"
            android:strokeLineJoin="bevel"
            android:pathData="M40 120
            l40 -90
            l40 90
            l-16-35
            h-48"/>

        <path android:name="small_diamond" android:fillColor="#cc0000" android:pathData="M80 130l41 41l-41 41l-41 -41z"/>
        <path android:name="big_diamond" android:fillColor="#cc0000" android:pathData="M260 310l100 100l-100 100l-100 -100z"/>
    </group>
</vector>
圖5

代碼很簡(jiǎn)單,只有4條path。border路徑順序是1-2-3-4-5-6-7-8-1, a的路徑是a-b-c-d-e,small_diamond的路徑是e-f-g-h,big_diamond的路徑是i-j-k-l。這個(gè)xml文件只有1k。


文章有錯(cuò)誤的地方希望指正。
本文內(nèi)容都是一些基礎(chǔ)的東西,應(yīng)該都能掌握,主要介紹了vector、group、path、clip-path這些標(biāo)簽常用的屬性以及pathData屬性對(duì)應(yīng)的常用的指令,工作中掌握這些常用的知識(shí)就能比較熟練使用矢量圖了。后續(xù)文章會(huì)剖析一些不常用的屬性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容