1 背景
最近因為一些個人私事導致好久沒寫博客了,多事之年總算要過去了,突然沒了動力,所以趕緊先拿個最近項目中重構的一個小知識點充下數,老題重談。
在我們App開發中大家可能都會有過如下痛疾(程序員和設計妹妹注意嘍):
-
好多小的圖標好煩人,又占體積還要考慮分辨率,一拉伸就模糊等。
-
同一個圖標不同狀態還有不同顏色的多張。
-
總是幻想IOS、Android、Web等對於一個圖標只切一次圖多好。
如果你有過類似的痛疾那么下面討論的故事就是一個完美的解決方案,當然了,采用下面方案對於重型應用或者固件級的優化才會十分明顯,對於普通小應用那就要看自己了。
2 PNG、IconFont、SVG理論
PNG為位圖,是由不同的排列和染色的像素點組成的圖像,當放大位圖時就能清晰的看見無數個小方塊(像馬賽克一樣),所以位圖的擴大實質是增加單個像素點的大小,故而導致在不同分辨率表現非常糟糕。
SVG為可縮放矢量圖,它不會像位圖一樣因為縮放而讓圖片質量下降,他在不同分辨率的表現都一樣清晰。
IconFont是WebFont形式的圖標,你可以把他認為就是WebFont,該類東東的制作來自於SVG矢量圖,所以它不會像位圖一樣因為縮放而讓圖片質量下降,他在不同分辨率的表現都一樣清晰。
下面我們簡單給出這幾種格式圖標素材的對比:
圖標類型 | 構成 | 優勢 | 劣勢 |
---|---|---|---|
PNG | 獨立像素點 | 可以實現各種色彩及真實畫面的復現 | 體積比較大,縮放和旋轉易失真 |
SVG | 向量 | 體積較小,縮放和旋轉不失真 | 制作色彩變化太多的圖象難度比較大 |
IconFont | WebFont | 體積較小,縮放和旋轉不失真 | 基本不支持多色彩圖像,一般都是單色 |
有了上面這些對比之后,在正式開始實戰之前我們先來簡單粗暴了解下SVG與IconFont在App中的優點吧,不然下文就沒有存在的意義嘍,如下:
-
圖標集中處理,設計妹妹只用出一次圖嘍。
-
減少重型應用或者固件大小,因為IconFont是字符串而不是圖片,SVG的體積也小於PNG。
-
因為IconFont的繪制原理和普通文本一樣,所以運行時會占用更小的內存空間。
聽着挺誘人,好吊的樣子,實際下面我們來看一下你就明白啦。
3 PNG、IconFont、SVG實戰
有了上面的理論基礎,下面我們就來看下他們各自的使用方法;關於PNG的使用這里就不介紹了,沒啥新鮮的,從Android一出來支持到現在的格式,要是還不會那就真該拖出去亂燉了。
3-1 IconFont使用實例
哈哈,這個其實阿里媽媽MUX團隊早就給大家搞好強大的庫和相關使用文檔了,詳細可以點我查看。當然嘍,有的公司會搞自己的庫服務器,自己去搭建,有的公司會借助阿里媽媽提供的平台直接去制作;不管哪種平台其實實質都一樣,沒啥可說的,原理一樣,只是我們可能會自己依據需求自定義一些支持IconFont的拓展TextView、ImageView等,萬變不離其宗,由於確實很簡單,下面我們給一個非常簡單的例子描述就行了。如下以阿里巴巴IconFont平台為例:
-
從平台選擇要使用到的圖標下載到本地,復制ttf字體文件到項目assets目錄下。
-
打開從IconFont平台下載下來的demo.html文件,找到圖標相對應的HTML字符碼。
-
打開項目res/values/strings.xml,添加string字符串值為上面字符碼。
-
注意:上面2和3步在有些自己搭建的平台上會自動用代碼生成一個iconfont.xml的string文件,這樣就不用自己手動去從demo.html復制到res下了,而是直接將iconfont.xml復制到res,不清楚那么牛逼的阿里為何沒這么做。
-
打開布局文件activity_main.xml,添加上面3生成的string值到TextView(這里只以TextView為例演示,實際中一般我們會直接拓展定義TextView等的)。如下:
-
1234
<
TextView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"@string/icon_font_add"
/>
-
接着給該TextView指定使用IconFont的文字文件。如下:
-
123
Typeface iconfont = Typeface.createFromAsset(getAssets(),
"iconfont.ttf"
);
TextView textview = (TextView)findViewById(R.id.test);
textview.setTypeface(iconfont);
-
至此IconFont的使用就搞定了,是不是很簡單而且超級贊,優劣自行腦補。
這玩意超級簡單,主要腦動力在美工妹妹,和咱程序員關系不是很大,所以直接用即可,不做Demo演示了,我們項目中也用了很多這玩意。
3-2 SVG使用實例
說到這貨,哈哈上面搞IconFont的美工妹妹一定知道,IconFont制作的來源就是SVG,只是SVG制作比IconFont容易且使用更加靈活而已。下面我們下來看一下SVG圖片直接用Txt打開后的d屬性,至於為啥看它,請自行腦補。PS:這貨除過控件支持以外還可以直接扔給WebView去顯示,因為其本質就可以說是HTML相關的東東。
SVG Path Data:
前提先說好,SVG和PathData都是可以通過美工的工具生成的,下面之所以介紹PathData只是一種簡單的背景了解而已。如下是一張SVG格式的圖片(再來一層像不像超人標示,哈哈):
我們現在拿txt工具把他打開(而不是圖片瀏覽器),如下:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="4cm" height="4cm" viewBox="0 0 400 400"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<title>Example triangle01- simple example of a 'path'</title>
<desc>A path that draws a triangle</desc>
<rect x="1" y="1" width="398" height="398"
fill="none" stroke="red" stroke-width="3" />
<path d="M 100 100 L 300 100 L 200 300 z"
fill="yellow" stroke="red" stroke-width="10" />
</svg>
握草!可以很明顯的看見PathData不就是SVG圖片里path節點下的d屬性值么?為了能夠看明白上面這個d屬性值和上面貼的圖片關系,我們先來看幾個科普。
SVG Path Data命令解釋(注意:每個命令都有大小寫形式,大寫代表后面的參數是絕對坐標,小寫表示相對坐標,每個參數之間用空格或逗號區分):
M/m命令:moveto移動到繪制點,后面跟上坐標系點對即可。
Z/z命令:closepath一段路徑等的閉合點,無參數。
L/l命令:lineto繪制一個點到另一個點的直線,多個坐標對可以繪制折線,后面跟上坐標系點對即可。
H/h命令:lineto從當前點到指定x點繪制水平直線,多個x無意義,后面跟坐標系x點即可。
V/v命令:lineto從當前點到指定x點繪制垂直直線,多個y無意義,后面跟坐標系y點即可。
C/c命令:cubic bezier三次貝塞爾曲線。
S/s命令:cubic bezier三次貝塞爾曲線。
Q/q命令:quatratic bezier二次貝塞爾曲線。
T/t命令:quatratic bezier二次貝塞爾曲線。
A/a命令:elliptical arc圓弧線。
好了,到這里你至少知道上面那個類超人圖標的東東XML是啥意思了,有這些就足夠了,下面我們開搞。
Android L時代的局部SVG先驅VectorDrawable和AnimatedVectorDrawable:
下面我們先來看下Android L時代Google給我們放的大招,牛逼的SVG支持,其中支持控件有VectorDrawable和AnimatedVectorDrawable,遺憾的是這兩控件在support包木有,可喜的是有現成的第三方開源兼容包。VectorDrawable的父類是Drawable,Drawable的父類是Object;AnimatedVectorDrawable的父類也是Drawable;從這就能看出來Android L拓展的SVG控件實質也是一個Drawable。
VectorDrawable的創建可以通過一個<vector>
節點的XML進行定義,下面我們來看看這些相關節點屬性的含義:
<vector>
:定義一個Vector Drawable。
android:name
定義這個VectorDrawable的名字;
android:width
定義本質的幾何寬度,尺寸標准隨意,一般為dp;
android:height
定義本質的幾何高度,尺寸標准隨意,一般為dp;
android:viewportWidth
定義viewport寬度,viewport是將path繪制在上面的一個虛擬畫布;
android:viewportHeight
定義viewport高度,同上;
android:tint
定義Drawable的着色,默認沒有色彩;
android:tintMode
定義着色模式,默認是src_in;
android:autoMirrored
定義圖片是否需要鏡像反轉,當布局方向是RTL,即從右到左布局時才有用;
android:alpha
設置圖片的透明度;
<group>
:定義一個Path組或者子組。
android:name
定義組的名字;
android:rotation
定義組的旋轉角度;
android:pivotX
定義縮放或者旋轉中軸點x坐標,是虛擬畫布中的坐標;
android:pivotY
同上,定義y坐標;
android:scaleX
定義縮放x維;
android:scaleY
定義縮放y維;
android:translateX
定義x縮放,是虛擬畫布中的坐標;
android:translateY
定義y縮放,是虛擬畫布中的坐標;
<path>
:定義一個被繪制的Path。
android:name
定義path的名字;
android:pathData
定義路徑采用了SVG文件里d標簽中的path值,這些值繪制在虛擬畫布上;
android:fillColor
定義路徑填充顏色;
android:strokeColor
定義path的外輪廓顏色;
android:strokeWidth
路徑的寬度;
android:strokeAlpha
一個路徑的透明度;
android:fillAlpha
全路徑透明度;
android:trimPathStart
開始路徑的百分比,0-1;
android:trimPathEnd
結束路徑的百分比,0-1;
android:trimPathOffset
轉換區域0-1;
android:strokeLineCap
設置線的頂路徑,圓還是方等;
android:strokeLineJoin
設置線連接處路徑方式;
android:strokeMiterLimit
設置線的修飾;
<clip-path>
:定義路徑裁剪,只適用於當前組或者子項。
android:name
定義裁剪路徑的名字;
android:pathData
定義路徑采用了SVG文件里d標簽中的path值;
扯了這么多,接下來我們舉個例子吧,哈哈,好在強大的AS已經支持在drawable下右鍵新建Vector資源了,支持的還是滿強大的,SVG或者MD的圖片直接到我們需要的xml文件一步生成。牛叉的一逼,再也不像剛出來那會搞個SVG2XML需要第三方工具了,如下是我通過AS選擇的一副圖片生成的XML,如下:
生成XML如下:
1
2
3
4
5
6
7
8
9
10
|
<!-- vectordrawable.xml >
<
vector
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:width
=
"100dp"
android:height
=
"100dp"
android:viewportWidth
=
"24.0"
android:viewportHeight
=
"24.0"
>
<
path
android:fillColor
=
"#FF000000"
android:pathData
=
"M7.52,21.48C4.25,19.94 1.91,16.76 1.55,13H0.05C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32zm0.89,-6.52c-0.19,0 -0.37,-0.03 -0.52,-0.08 -0.16,-0.06 -0.29,-0.13 -0.4,-0.24 -0.11,-0.1 -0.2,-0.22 -0.26,-0.37 -0.06,-0.14 -0.09,-0.3 -0.09,-0.47h-1.3c0,0.36 0.07,0.68 0.21,0.95 0.14,0.27 0.33,0.5 0.56,0.69 0.24,0.18 0.51,0.32 0.82,0.41 0.3,0.1 0.62,0.15 0.96,0.15 0.37,0 0.72,-0.05 1.03,-0.15 0.32,-0.1 0.6,-0.25 0.83,-0.44s0.42,-0.43 0.55,-0.72c0.13,-0.29 0.2,-0.61 0.2,-0.97 0,-0.19 -0.02,-0.38 -0.07,-0.56 -0.05,-0.18 -0.12,-0.35 -0.23,-0.51 -0.1,-0.16 -0.24,-0.3 -0.4,-0.43 -0.17,-0.13 -0.37,-0.23 -0.61,-0.31 0.2,-0.09 0.37,-0.2 0.52,-0.33 0.15,-0.13 0.27,-0.27 0.37,-0.42 0.1,-0.15 0.17,-0.3 0.22,-0.46 0.05,-0.16 0.07,-0.32 0.07,-0.48 0,-0.36 -0.06,-0.68 -0.18,-0.96 -0.12,-0.28 -0.29,-0.51 -0.51,-0.69 -0.2,-0.19 -0.47,-0.33 -0.77,-0.43C9.1,8.05 8.76,8 8.39,8c-0.36,0 -0.69,0.05 -1,0.16 -0.3,0.11 -0.57,0.26 -0.79,0.45 -0.21,0.19 -0.38,0.41 -0.51,0.67 -0.12,0.26 -0.18,0.54 -0.18,0.85h1.3c0,-0.17 0.03,-0.32 0.09,-0.45s0.14,-0.25 0.25,-0.34c0.11,-0.09 0.23,-0.17 0.38,-0.22 0.15,-0.05 0.3,-0.08 0.48,-0.08 0.4,0 0.7,0.1 0.89,0.31 0.19,0.2 0.29,0.49 0.29,0.86 0,0.18 -0.03,0.34 -0.08,0.49 -0.05,0.15 -0.14,0.27 -0.25,0.37 -0.11,0.1 -0.25,0.18 -0.41,0.24 -0.16,0.06 -0.36,0.09 -0.58,0.09H7.5v1.03h0.77c0.22,0 0.42,0.02 0.6,0.07s0.33,0.13 0.45,0.23c0.12,0.11 0.22,0.24 0.29,0.4 0.07,0.16 0.1,0.35 0.1,0.57 0,0.41 -0.12,0.72 -0.35,0.93 -0.23,0.23 -0.55,0.33 -0.95,0.33zm8.55,-5.92c-0.32,-0.33 -0.7,-0.59 -1.14,-0.77 -0.43,-0.18 -0.92,-0.27 -1.46,-0.27H12v8h2.3c0.55,0 1.06,-0.09 1.51,-0.27 0.45,-0.18 0.84,-0.43 1.16,-0.76 0.32,-0.33 0.57,-0.73 0.74,-1.19 0.17,-0.47 0.26,-0.99 0.26,-1.57v-0.4c0,-0.58 -0.09,-1.1 -0.26,-1.57 -0.18,-0.47 -0.43,-0.87 -0.75,-1.2zm-0.39,3.16c0,0.42 -0.05,0.79 -0.14,1.13 -0.1,0.33 -0.24,0.62 -0.43,0.85 -0.19,0.23 -0.43,0.41 -0.71,0.53 -0.29,0.12 -0.62,0.18 -0.99,0.18h-0.91V9.12h0.97c0.72,0 1.27,0.23 1.64,0.69 0.38,0.46 0.57,1.12 0.57,1.99v0.4zM12,0l-0.66,0.03 3.81,3.81 1.33,-1.33c3.27,1.55 5.61,4.72 5.96,8.48h1.5C23.44,4.84 18.29,0 12,0z"
/>
</
vector
>
|
如下是顯示結果:
關於VectorDrawable的其他屬性請自行設置查看效果,重點一般都是pathData,我們可以在這里獲得規范,你要是足夠牛叉了解所有規范,那你可以做出來很牛逼的效果的,我暫時還不牛逼,哈哈。有了上面的VectorDrawable基礎后我們來看下AnimatedVectorDrawable,這玩意其實就是通過ObjectAnimator和AnimatorSet對VectorDrawable進行屬性等動畫操作的一個Drawable;這玩意通常被定義成三個獨立的xml文件,具體如下:
第一步,創建<vector>
元素的矢量資源,放置在res/drawable/下,動畫一般發生在group或者path節點上,所以如果我們要給這些節點添加動畫,一定要保證名字的獨立。如下是一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!-- vectordrawable.xml -->
<
vector
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:height
=
"64dp"
android:width
=
"64dp"
android:viewportHeight
=
"600"
android:viewportWidth
=
"600"
>
<
group
android:name
=
"rotationGroup"
android:pivotX
=
"300.0"
android:pivotY
=
"300.0"
android:rotation
=
"45.0"
>
<
path
android:name
=
"v"
android:fillColor
=
"#000000"
android:pathData
=
"M300,70 l 0,-70 70,70 0,0 -70,70z"
/>
</
group
>
</
vector
>
|
第二步,創建<animated-vector>
元素的矢量資源動畫,放置在res/drawable/下,這里主要就是將動畫與前面的VectorDrawable進行關聯。如下是一個例子:
1
2
3
4
5
6
7
8
9
10
|
<!-- avd.xml -->
<
animated-vector
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:drawable
=
"@drawable/vectordrawable"
>
<
target
android:name
=
"rotationGroup"
android:animation
=
"@anim/rotation"
/>
<
target
android:name
=
"v"
android:animation
=
"@anim/path_morph"
/>
</
animated-vector
>
|
第三步,創建<objectAnimator>
元素的一個或多個對象動畫器,放置在res/animator下,這玩意其實就是我們之前認識的真實的Android屬性動畫ObjectAnimator或AnimatorSet嘍,沒啥意思,不過還是看下它是怎么控制pathData的吧,如下:
1
2
3
4
5
6
|
<!--rotation.xml 將目標旋轉360度-->
<
objectAnimator
android:duration
=
"6000"
android:propertyName
=
"rotation"
android:valueFrom
=
"0"
android:valueTo
=
"360"
/>
|
1
2
3
4
5
6
7
8
9
|
<!--path_morph.xml 將目標路徑動畫化,路徑可以寫入string.xml中-->
<
set
xmlns:android
=
"http://schemas.android.com/apk/res/android"
>
<
objectAnimator
android:duration
=
"3000"
android:propertyName
=
"pathData"
android:valueFrom
=
"M300,70 l 0,-70 70,70 0,0 -70,70z"
android:valueTo
=
"M300,70 l 0,-70 70,0 0,140 -70,0 z"
android:valueType
=
"pathType"
/>
</
set
>
|
最后我們在Java代碼中將該drawable進行動畫啟動即可,如下:
1
2
3
|
if
(drawable
instanceof
Animatable) {
((Animatable) drawable).start();
}
|
到此AnimatedVectorDrawable的基本情況就OK了,沒啥懸念的,重點依舊是pathData的定義,所以如果項目有需求還是要好好搞搞的,暫時木有需求,因為我們用的IconFont方案。
非Android L時代的局部SVG兼容VectorDrawable和AnimatedVectorDrawable解決方案:
對於做固件App來說可能上面介紹的Android L提供的控件足以滿足用來使用SVG了,但是對於第三方開發者來說確實還是挺悲催的,兼容性問題一大堆,所以這里給出一個兼容Android L以下版本的SVG控件使用開源庫。如下:
vector-compat
這個庫可以基本滿足替換原生Android L對SVG的兼容性,具體用法自行查看README文檔和查看人家大神的Demo吧,請叫我雷鋒,不再過多說明。
為了效率的完全直接使用SVG拓展優化方案:
可以看見,上面的那么多控件雖說叫支持SVG,其實實質是只支持SVG圖片里面的一部分而已,主要就是PathData;所以為了完全直接支持SVG圖片而不是SVG生成的XML,騰訊微信團隊等又給出了一個實現思路,不得不感慨BAT的一些牛逼之處。
該方案微信團隊已經進行過效率、性能、灰度等測試驗證,而且貌似微信都已經完全使用該方案進行資源優化了,這里不再細說,有興趣的自行查閱資料吧,坐等我們項目能從IconFont切到這種方案,嗚嗚!!!
PS:關於Android AnimatedVectorDrawable等搞出來的各種腦洞大開的動畫場景請君自行腦補,自己暫時沒那么多沒時間,所以就不多說了,等項目有需求了再說。
PPPS:其實Android L系統的圓圈型ProcessBar之所以那么玄,實質就是這東東搞的