1. 圖像基礎
圖像分為矢量圖和柵格圖兩種,這兩張格式最直觀的區別是矢量圖可以無限放大而不失真,而矢量圖放大或縮小就會因為失真而變得模糊,可以參加如下圖片。
1.1柵格圖
柵格圖也稱位圖,它由像素點組成,每個像素點分配特定的色值和位置。我們平時生活和工作中遇到的圖像大部分都是柵格圖,它對圖片在空間和亮度上都做了離散化。我們先拿最簡單的一張黑白位圖舉例:
假如這個圖片是300x300的,即它由300x300個像素點組成,每個像素點要么是黑色的,要么是白色的。
如果像讓圖片表達更豐富點,可以使用灰度圖,參見下面這張圖像處理課中的經典圖片。一般灰度圖每個像素點用0~255表示灰度等級,0表示白色,255表示黑色,這樣的話一張300x300的圖片就可以用 unsigned int8[300][300]來表示這張圖片。
那支持彩色圖片,也同樣原理了,每個像素點支持RGB三原色,三原色可以產生其他顏色。正常情況下RGB每種顏色也是支持0~255個色度。為了支持圖片透明,有ARGB格式,前面的A表示透明度,這時一個像素點大小為int32。寫程序時有時圖片占用內存太大,會把加載時的RGB888改成RGB555,它的原理即讓支持256個色度的顏色改為了128個色度,節省了空間犧牲了顏色的飽和度。
上面講到的圖片放大或者縮小,可以看數字圖像處理教程中的公式,放大或者縮小只是對原圖像素位置做矩陣運算得到新的位置,對于放大新產生的像素點或者縮小時不是整數位的像素點,需要做插值處理。由此可以知道失真是必然的。
1.2 矢量圖
矢量的定義為既有大小又有方向的幾何對象,那矢量圖顧名思義為由矢量組成的圖像。矢量圖由矢量定義的直線和曲線組成,可以放在特定位置使用顏色填充,它基于數學公式計算獲得,所以無論放大還是縮小都不會失真。
可以舉個抽象的栗子,圖像是個紅色的圓形,或者是一個綠色的等邊三角形。這樣的圖像無論你放大多少倍都不會失真的。如下圖為字體庫導出的矢量圖。
根據兩種圖像的實現原理,不難得出他們的優缺點:矢量圖只需要存儲函數和一些關鍵點和方向信息,所以存儲空間非常小,對它進行放大縮小旋轉等操作也不會有任何失真。由于圖像是臨時計算出來的,對于運算資源需求大,同時對于顏色描述能力不夠。
2. SVG在android中的使用
2.1 SVG簡介
SVG全稱為Scalable Vector Graphics,是由(W3C)聯盟指定的基于XML的矢量圖形格式,是一個開放的標準。
它以<svg>標簽開始,以</svg>標簽結束。標簽可以添加一些屬性,也可以嵌套一些其他標簽。下面我們可以看幾個例子
<svg width="100%" height="100%" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black"stroke-width="2" fill="red"/>
</svg>
<svg width="100%" height="100%" version="1.1">
<polygon points="220,100 300,210 170,250 123,234" style="fill:#cccccc;stroke:#000000;stroke-width:1"/>
</svg>
<svg width="100%" height="100%" version="1.1">
<path d="M153 334
C153 334 151 334 151 334
C151 339 153 344 156 344
C164 344 171 339 171 334
C171 322 164 314 156 314
C142 314 131 322 131 334
C131 350 142 364 156 364
C175 364 191 350 191 334
C191 311 175 294 156 294
C131 294 111 311 111 334
C111 361 131 384 156 384
C186 384 211 361 211 334
C211 300 186 274 156 274"
style="fill:white;stroke:red;stroke-width:2"/>
</svg>
通過上面的幾個例子,基本可以窺視SVG用法,通過一些標簽和屬性,完成各種圖形。查看文檔知道它有圓形、矩形、多邊形支持一些常規需求,比較復雜的圖像可以使用路徑(path)來實現。當然復雜的圖形,可以使用其他工具去完成然后生成路徑。
2.2 SVG圖創建和獲取
1)官方自帶Meterial Icon,官方自帶了一套圖片庫,可以滿足常用場景需求。系統鼠標選中drawable文件夾, New -> Vector Asset,然后出現
上面默認勾選"Material Icon",然后點擊Android圖標,進入官方自帶的SVG圖標庫:
我們可以選一個圖標看一下(點擊右邊的Preview可以預覽圖片效果):
2)本地導入。上面的提到的勾選框,選擇Local file可以導入本地已有的SVG圖片或PSD。可以找專業設計師設計圖標,也可以從一些圖片庫上面去下載需要的圖標,例如阿里的iconfont(http://iconfont.cn/),還有一些第三方工具例如Vector Magic,將png、jpg等圖片格式導出SVG,也有在線導出svg的工具如http://editor.method.ac/。
2.3 在Android中應用
正常來說,矢量圖在Android應用很簡單,我們把它當做普通圖片來使用就可以了。現在的主要問題是,Android是從5.0版本才開始支持矢量圖的,為了適配低版本手機,需要額外做一些工作:
1)ImageView中矢量圖的使用。一般使用兼容包里的AppCompatImageView替換ImageView,在布局中使用“app:srcCompat”代替“app:src”。如果當前的Activity繼承了AppCompatActivity,可以直接使用ImageView,編譯時會自動替換,不過屬性還需要使用“”app:srcCompat。
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_clear_black_24dp"
/>
- background中使用矢量圖。低版本普通控件background是不支持直接使用矢量圖的,對矢量圖的支持依賴于StateListDrawable,InsetDrawable,LayerDrawable,LevelListDrawable,RotateDrawable。所以可以通過使用selector來支持矢量圖。
<Button
android:layout_width="200dp"
android:layout_height="50dp"
android:background="@drawable/selector"
/>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_clear_black_24dp" android:state_pressed="true"/>
<item android:drawable="@drawable/ic_clear_black_24dp"/>
</selector>
這樣還不夠,還需要在Activity前面添加如下代碼
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
跟進源碼可以看到只是修改一個靜態變量的值,所以圖省事可以在application里面添加,這樣所有的Activity控件都支持vector了,但是看到它的注釋里提到,開啟這個flag會增加內存開銷,并且影響更新Configuration實例。具體的影響目前還沒有測試。
開啟這個這個flag后,android:drawableLeft、RadioButton已經ImageView的src屬性都可以正常使用矢量圖了。
3. 矢量動畫
3.1官方矢量動畫
Android提供了AnimatedVectorDrawable來實現矢量動畫。它有幾個重要方法需要了解:
start:開始播放動畫
stop:停止動畫
registerAnimationCallback: 注冊一個監聽者,可以監聽到動畫開始和結束事件。矢量圖形的很多屬性都支持動畫,最常用的主要是下面三類:
- 1)屬性變換類。主要屬性包括alpha、rotation、scaleX、scaleY、translateX、translateY等,這個跟常見的補間動畫一樣。
- 2)路徑繪制類,這個主要使用path標簽的trimPathStart和trimPathEnd屬性。它可以按照path的路徑逐步畫出或者擦除圖片。我們可以看下圖(盜圖:),由path繪制的一個圓形和一個對號,可以使用下面代碼實現路徑繪制:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:interpolator/linear"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
- 路徑變換類,這個主要是指定兩個path,系統自動生成動畫,從第一個path圖形變換為第二個path圖形。但是并不是所有的path間都支持這種變換,如果兩個path間不支持時系統會拋出異常。這里有一個開源項目可以幫你優化path使其支持路徑變換動畫。動畫代碼如下,valueFrom指向動畫開始的path,valueTo指向動畫變換后的path
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="pathData"
android:valueFrom="@string/path_begin"
android:valueTo="@string/path_end"
android:valueType="pathType"
android:interpolator="@android:anim/accelerate_interpolator"/>
3.2 Lottie,讓矢量動畫更簡單
上面講到了官方矢量動畫的實現,客觀講使用起來還是有點復雜的,首先要準備相關的矢量圖資源,然后再寫動畫文件,并且根據上面講到的知識,一些復雜動畫實現起來還是很麻煩的。另外一個動畫好不容易在Android平臺上實現了,iOS上面可能還需要做同樣的實現工作,介于以上原因,airbnb實現了一套通用矢量動畫方案lottie,可以支持android、iOS、RN和Web。它的思路就是設計同學使用相關工具(例如After Effects)做出矢量動畫,導出為一個json文件,然后這個json文件就可以拿來在不同的系統上使用。我們看下Android中的使用:
- 1)引入庫
compile 'com.airbnb.android:lottie:2.1.0'
- 使用動畫。lottie最低支持到api 16,支持從assets中和res/raw中加載動畫資源,官方建議使用raw,因為可以使用R文件靜態引用到資源文件,下面一個例子:
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_rawRes="@raw/hello_world"
// 或者
app:lottie_fileName="hello_world.json"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
就這么簡單就完成了矢量動畫的顯示,當然還有一些其他設置例如停止動畫、監聽動畫等可以參考官方文檔
另外lottie還支持從網絡下載矢量動畫資源:
LottieAnimationView animationView = ...
// This allows lottie to use the streaming deserializer mentioned above.
JsonReader jsonReader = new JsonReader(new StringReader(json.toString()))
animationView.setAnimation(jsonReader)
animationView.playAnimation
使用起來非常簡潔,這里有一個網站提供大量動畫素材,大家可以參考使用。