一、動畫基礎 |
---|
本質
每幀繪制不同的內容。
基本過程
開始動畫后,調用View的invalidate觸發重繪。重繪后檢查動畫是否停止,若未停止則繼續調用invalidate觸發下一幀(下一次重繪),直到動畫結束。
重繪時View的draw方法會被調用,根據動畫的進行繪制不同的內容,如某個被繪制元素的大小變化、角度旋轉、透明度變化等,這樣即會產生動畫。
動畫的推進過程一般都會有一個變化量,這個變量會被用到draw方法內元素的繪制。一般的變量都是時間,也可以是手指移動、傳感器等任何其他的變量。
Android中的動畫支持
Animation:早期實現的讓View整體做動畫的類。能讓View做Matrix(移動、縮放、旋轉、3D旋轉)和Alpha(透明)的動畫。
Animator:有硬件加速后為做動畫實現的類。能方便的讓View整體做動畫;也可以只產生隨時間變化的變量,用來在onDraw里做繪圖級的動畫。比Animation靈活很多。
AnimationDrawable:圖片逐幀動畫。主要用來播放提前制作好的動畫。
在哪個級別做動畫
讓整個View做動畫(比如整個View平移、旋轉等)很簡單方便,一般調用幾行代碼就行。我把它稱作View級的動畫。
在View的draw/onDraw里通過Canvas來繪制時做動畫更靈活,更精細,能力更強大。我把它稱作繪圖級的動畫。(View級的動畫本質上也是這么做的,只是Android系統幫我們做了大部分工作)
繪圖級的動畫
這篇文章主要講繪圖級的動畫。
下面來一段繪圖級動畫的典型實現:
class
MyView
extends
View {
void
startAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.start();
invalidate();
}
protected
void
onDraw(Canvas canvas) {
if
(animator.isRunning()) {
float
ratio = (Float)animator.getAnimatedValue();
canvas.rotate(ratio*
360
);
canvas.drawBitmap(bitmap,
0
,
0
,
null
);
invalidate();
}
...
}
}
|
有了不斷變化的ratio變量,繪圖級動畫就可以大展身手了。
繪圖級動畫的強大能力來自繪圖API的強大能力,下面主要講繪圖API。
二、繪圖API |
---|
Matrix
Canvas.[translate,scale,rotate,skew]方法
Matrix.set/pre/post[translate,scale,rotate,skew]方法
平移、縮放、旋轉、斜切。
從使用API的角度來看,我們通過調用Canvas.translate等方法,可以使后續在此Canvas上繪制操作的繪制區域變化,如translate(5,0),則后續所有繪制操作的繪制區域都會向右移動5個像素。
原理:Canvas里有一個Matrix,Canvas上的這幾個調用都會最終調用到Matrix.pre*。這個Matrix保存整個變換過程。當有Canvas.draw時,要繪制的點都會經過Matrix.mapPoints方法做一個映射。於是產生我們期望的變換效果。(事實上映射的時候只需要映射關鍵點,其他的是插值來的)
關於Matrix的更多信息
set/pre/post的區別:set是設置,沖掉以前的數據。pre是前乘,post是后乘,根本上講就是生效順序不同。具體表現效果可在網上搜索資料。
setPolyToPoly:與mapPoints方法相反,mapPoints是通過矩陣把原始點映射為目標點。setPolyToPoly是輸入原始點和映射后的目標點,計算出這個矩陣。
Camera:有透視效果的3D旋轉。Camera是一個生成Matrix的工具類。可用來生成有透視效果的3D旋轉。
Canvas.draw*方法
Canvas.draw-Point/s
Canvas.draw-Line/s
Canvas.draw-Rect,RoundRect,Circle,Oval,Arc,Path
Canvas.draw-Text
Canvas.draw-Bitmap,BitmapMesh
Canvas.draw-Color,Paint
這些方法都表示繪制一個區域。繪制的區域中究竟填充什么顏色,由Paint決定。
Color,Paint,Bitmap,BitmapMesh這幾個則除了指定繪制區域外,還指定了填充內容。
Path功能比較強大,可自行組織成任何形狀,還可以用貝塞爾曲線。
這些方法基本上都很好理解,從名字上即可看出其功能。這里重點提一下drawBitmapMesh。
drawBitmapMesh是輸入一個網格模型,繪制出的圖片內容將依據這個網格來扭曲。可以想像成把圖片畫在一塊有彈性的布上,當我們把布的某些區域扯動的時候,會形成畫面扭曲效果。
示例:假設有個30x30大小的圖片,我們建立這樣的網格輸入:
0,0, 15,0, 30,0,
0,15, 15,15, 30,15,
0,30, 15,30, 30,30
則圖片會原樣輸出,沒有任何扭曲。
如果我們建立這樣的網格輸入:
0,0, 15,12, 30,0,
0,15, 15,15, 30,15,
0,30, 15,30, 30,30
Alpha通道
每個Color里可以有四個通道ARGB,其中RGB是紅綠藍,A即Alpha通道,它通常的作用是用來作為此顏色的透明度。
因為我們的顯示屏是沒法透明的,因此最終顯示在屏幕上的顏色里可以認為沒有Alpha通道。Alpha通道主要在兩個圖像混合的時候生效。
默認情況下,當一個顏色繪制到Canvas上時的混合模式是這樣計算的:(RGB通道) 最終顏色 = 繪制的顏色 + (1 - 繪制顏色的透明度) × Canvas上的原有顏色。
注意:
1.這里我們一般把每個通道的取值從0到255映射到0到1的浮點數表示。
2.這里等式右邊的“繪制的顏色"、“Canvas上的原有顏色”都是經過預乘了自己的Alpha通道的值。如繪制顏色:0x88ffffff,那么參與運算時的每個顏色通道的值不是1.0,而是(1.0 * 0.53125 = 0.53125)。
使用這種方式的混合,就會造成后繪制的內容以半透明的方式疊在上面的視覺效果。
其實還可以有不同的混合模式供我們選擇,用Paint.setXfermode,指定不同的PorterDuff.Mode。
下表是各個PorterDuff模式的混合計算公式:(D指原本在Canvas上的內容dst,S指繪制輸入的內容src,a指alpha通道,c指RGB各個通道)
ADD | Saturate(S + D) |
CLEAR | [0, 0] |
DARKEN | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] |
DST | [Da, Dc] |
DST_ATOP | [Sa, Sa * Dc + Sc * (1 - Da)] |
DST_IN | [Sa * Da, Sa * Dc] |
DST_OUT | [Da * (1 - Sa), Dc * (1 - Sa)] |
DST_OVER | [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] |
LIGHTEN | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] |
MULTIPLY | [Sa * Da, Sc * Dc] |
SCREEN | [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] |
SRC | [Sa, Sc] |
SRC_ATOP | [Da, Sc * Da + (1 - Sa) * Dc] |
SRC_IN | [Sa * Da, Sc * Da] |
SRC_OUT | [Sa * (1 - Da), Sc * (1 - Da)] |
SRC_OVER | [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] |
XOR | [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] |
可以發現,我們之前的默認混合模式其實就是SRC_OVER。
通過選擇其他的PorterDuff模式,我們可以達到一些特殊的效果:
使用DST_OVER的話,相當於后繪制的內容作為背景在底下。
使用DST_IN/DST_OUT的話,可以裁剪Canvas里的內容,或用一張帶alpha的圖片mask指定哪些區域顯示/不顯示。
通過選擇SRC_ATOP可以只在Canvas上有內容(不透明)的地方繪制。
用一張示例圖來查看使用不同模式時的混合效果(src表示輸入的圖,dst表示原Canvas上的內容):
填充顏色
之前說過Canvas.draw*指定了繪制的區域。而區域里的填充顏色是由Paint來指定的。
Paint.setColor指定純色。
Paint.setShader可指定:BitmapShader, LinearGradient, RadialGradient, SweepGradient, ComposeShader。
BitmapShader:圖片填充。
LinearGradient, RadialGradient, SweepGradient:漸變填充。
ComposeShader:疊加前面的某兩種。可選擇PorterDuff混合模式。
如果既調用了setColor,又調用了setShader,則setShader生效。如果同時用setColor或setAlpha設置了透明度,則透明度也會生效。(會和Shader的透明度疊加)
如果使用drawBitmap輸入一個只有alpha的圖片(可用Bitmap.extractAlpha方法獲得),則會以alpha圖片為mask,繪制出shader/color的顏色。
ColorFilter
通過ColorFilter可以對一次繪制的所有像素做一個通用處理。
Paint.setColorFilter: LightingColorFilter, PorterDuffColorFilter, ColorMatrixColorFilter。
這可以整體上改變這一次draw的內容,比如讓顏色更暗、更亮等。
這里重點介紹下ColorMatrixColorFilter。
ColorMatrix是4x5矩陣,定義其每個元素如下:
{ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t }
則ColorMatrix的最終運算方式如下:
R' = a*R + b*G + c*B + d*A + e;
G' = f*R + g*G + h*B + i*A + j;
B' = k*R + l*G + m*B + n*A + o;
A' = p*R + q*G + r*B + s*A + t;
可在這里方便的試驗flash在線版。
繪圖API架構
整個繪制流水線大概如下:(我們能定義的部分用藍色表示)
考慮動畫實現的時候一般從兩個角度來思考:
宏觀角度:有幾個變化量,分別是什么。動畫從開始到結束的流程。
微觀角度:從某一幀上去想,在變化量為某個數值時的圖像,該怎么繪制。
把這兩者分開去想,就會比較清晰。
PPT里有示例,可以參照DEMO來熟悉: