Android圖形動畫


一、動畫基礎

本質

每幀繪制不同的內容。

 

基本過程

開始動畫后,調用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

則原本[15,0]的點會被繪制到[15,12]的位置上去。圖片繪制出來后,上面部分會缺一塊,形成用手把圖片從上邊中間位置往下拉的扭曲效果。但很銳利,上面缺的一塊是個三角形而不會是半圓型,通常我們希望的是半圓型,這就需要我們把這個網格建得密一些。

 

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可指定:BitmapShaderLinearGradientRadialGradientSweepGradientComposeShader

BitmapShader:圖片填充。

LinearGradient, RadialGradient, SweepGradient:漸變填充。

ComposeShader:疊加前面的某兩種。可選擇PorterDuff混合模式。

如果既調用了setColor,又調用了setShader,則setShader生效。如果同時用setColor或setAlpha設置了透明度,則透明度也會生效。(會和Shader的透明度疊加)

如果使用drawBitmap輸入一個只有alpha的圖片(可用Bitmap.extractAlpha方法獲得),則會以alpha圖片為mask,繪制出shader/color的顏色。

 

ColorFilter

通過ColorFilter可以對一次繪制的所有像素做一個通用處理。

Paint.setColorFilter: LightingColorFilterPorterDuffColorFilterColorMatrixColorFilter

這可以整體上改變這一次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來熟悉:

怎么做動畫.ppt

Android圖形系統簡介.ppt

GraphicsDemo.apk

GraphicsDemo_src.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM