1. Outline
本文主要從以下三個(gè)大的方面來說明一下2D Graphic 繪圖的一些相關(guān)函數(shù)及應(yīng)用。
- Color,Paint,Canvas,Typeface,Bitmap五個(gè)基本對(duì)象的概念,以及使用的方法
- SurfaceView在app中的使用
- 動(dòng)畫的使用
Android畫圖最基本的三個(gè)對(duì)象是(Color,Paint,Canvas)
三個(gè)類都存放在 android.graphics包下
- Color :顏色對(duì)象,相當(dāng)于現(xiàn)實(shí)生活中的 調(diào)料
- Paint : 畫筆對(duì)象,相當(dāng)于現(xiàn)實(shí)生活中畫圖用的 筆,主要的還是對(duì)‘畫筆’進(jìn)行設(shè)置
- Canvas : 畫布對(duì)象,相當(dāng)于現(xiàn)實(shí)生活中畫圖用的畫紙或者畫布
而Typeface是一個(gè)字體對(duì)象,同樣在android.graphics包,這個(gè)類的作用是獲取字體,創(chuàng)建字體,以及設(shè)置字體。
其中Paint,Canvas需要配合使用,而Color和Typeface可以在普通的View中單獨(dú)使用
2. Canvas
2.1 綜述
Class Overview
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
A Canvas works for you as a pretense, or interface, to the actual surface upon which your graphics will be drawn — it holds all of your "draw" calls. Via the Canvas, your drawing is actually performed upon an underlying Bitmap, which is placed into the window.
我們可以把這個(gè)Canvas理解成系統(tǒng)提供給我們的一塊內(nèi)存區(qū)域(但實(shí)際上它只是一套畫圖的API,真正的內(nèi)存是下面的Bitmap),而且它還提供了一整套對(duì)這個(gè)內(nèi)存區(qū)域進(jìn)行操作的方法,所有的這些操作都是畫圖API。
Drawing to a Canvas, is better when your application needs to regularly re-draw itself. Applications such as video games should be drawing to the Canvas on its own.
In the same thread as your UI Activity, wherein you create a custom View component in your layout, call invalidate() and then handle the onDraw() callback.
In the event that you're drawing within the onDraw() callback method, the Canvas is provided for you and you need only place your drawing calls upon it.
如果我們需要使用Canvas繪制自定義圖形
1)需要繼承 View 這個(gè)類
2)并要實(shí)現(xiàn)一個(gè)帶Context參數(shù)的構(gòu)造函數(shù),因?yàn)楦割愔?,沒有隱式無參的構(gòu)造函數(shù)
3)需重寫父類中的onDraw方法,一切的畫圖操作將在這進(jìn)行
2.2 獲取方式
Canvas的獲取方式有以下三種:
1 . 在View的OnDraw方法中,回調(diào)方法OnDraw會(huì)將Canvas做為參數(shù)傳入,方法中直接使用即可。
一個(gè)典型的Canvas使用方法如下圖所示。
In the event that you're drawing within the onDraw() callback method, the Canvas is provided for you and you need only place your drawing calls upon it.
To start, extend the View class (or descendant thereof) and define the onDraw() callback method. This method will be called by the Android framework to request that your View draw itself. This is where you will perform all your calls to draw through the Canvas, which is passed to you through the onDraw() callback.
對(duì)于這方法,view在繪制之后onDraw 方法不會(huì)被反復(fù)調(diào)用,需要我們調(diào)用View的invalidate方法來觸發(fā)相應(yīng)view的onDraw方法再次被調(diào)用。
The Android framework will only call onDraw() as necessary. Each time that your application is prepared to be drawn, you must request your View be invalidated by calling invalidate(). This indicates that you'd like your View to be drawn and Android will then call your onDraw() method (though is not guaranteed that the callback will be instantaneous).
這種方法的試用范圍,主要是不要求高顯示刷新速度的簡(jiǎn)單游戲.對(duì)于速度有較高要求的應(yīng)用中,可以使用下面的方法.
2 . 使用專門的SurfaceView的canvas來畫圖。這種方式最大的區(qū)別就是SurfaceView中定義了一個(gè)專門的線程來完成畫圖工作,應(yīng)用程序不需要等待View的刷圖,提高性能。這種方法主要用在游戲,高品質(zhì)動(dòng)畫方面的畫圖。
SurfaceView中使用SurfaceHolder.lockCanvas()來獲取Canvas,我們后面會(huì)在專門的SurfaceView的章節(jié)中講到Canvas的使用,這里暫時(shí)先跳過。
- 除了前面提到的兩種間接獲取的方式,我們也可以自己創(chuàng)建Canvas來使用。
However, if you need to create a new Canvas, then you must define the Bitmap upon which drawing will actually be performed. The Bitmap is always required for a Canvas. You can set up a new Canvas like this:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
?Canvas c = new Canvas(b);
Now your Canvas will draw onto the defined Bitmap. After drawing upon it with the Canvas, you can then carry your Bitmap to another Canvas with one of the Canvas.drawBitmap(Bitmap,...) methods. It‘s recommended that you ultimately draw your final graphics through a Canvas offered to you by View.onDraw() orSurfaceHolder.lockCanvas() .
需要注意的是,如果使用自定義的Canvas,我們最后依然需要通過調(diào)用系統(tǒng)提供的Canvas的Canvas.drawBitmap(Bitmap,...)方法,將canvas最終繪制出來。
2.3 使用
Canvas的使用很簡(jiǎn)單,我們主要就是使用系統(tǒng)提供的一些API來繪制指定圖形,主要的API有:
drawRect(RectF rect, Paint paint) //繪制區(qū)域,參數(shù)一為RectF一個(gè)區(qū)域
drawPath(Path path, Paint paint) //繪制一個(gè)路徑,參數(shù)一為Path路徑對(duì)象
drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //畫線
drawPoint(float x, float y, Paint paint) //畫點(diǎn)
drawText(String text, float x, floaty, Paint paint) //繪制文本,參數(shù)一是String類型的文本,參數(shù)二x軸,參數(shù)三y軸
drawOval(RectF oval, Paint paint)//畫橢圓,參數(shù)一是掃描區(qū)域?
drawCircle(float cx, float cy, float radius,Paint paint)// 繪制圓,參數(shù)一是中心點(diǎn)的x軸,參數(shù)二是中心點(diǎn)的y軸,參數(shù)三是半徑
?drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//畫弧,參數(shù)一是RectF對(duì)象,一個(gè)矩形區(qū)域橢圓形的界限用于定義在形狀、大小、電弧,參數(shù)二是起始角(度)在電弧的開始,參數(shù)三是掃描角度
3 Paint
3.1 綜述
Canvas在繪制具體形狀時(shí),一個(gè)必不可少的對(duì)象就是Paint對(duì)象。
這個(gè)對(duì)象就是我們前面提到的畫筆對(duì)象。
Class Overview
The Paint class holds the style and color information about how to draw geometries, text and bitmaps.
其實(shí)Paint對(duì)象就是一個(gè)包含了對(duì)畫筆各種屬性設(shè)置方法的類,其中主要的方法如下:
setARGB(int a, int r, int g, int b) // 設(shè)置 Paint對(duì)象顏色,參數(shù)一為alpha透明值
setAlpha(int a) // 設(shè)置alpha不透明度,范圍為0~255
setAntiAlias(boolean aa) // 是否抗鋸齒
setColor(int color) // 設(shè)置顏色,這里Android內(nèi)部定義的有Color類包含了一些常見顏色定義
setTextScaleX(float scaleX) // 設(shè)置文本縮放倍數(shù),1.0f為原始
setTextSize(float textSize) // 設(shè)置字體大小
setUnderlineText(booleanunderlineText) // 設(shè)置下劃線
這樣,我們首先設(shè)置好畫筆對(duì)象,然后使用相應(yīng)的畫筆來在Canvas上繪制具體的圖案,就可以產(chǎn)生如下圖效果。
3.2 Shader
在使用paint設(shè)置畫筆的同時(shí),我們也可以給畫筆來設(shè)置shader對(duì)象來為畫筆增加更多的效果。
Shader類專門用來渲染圖像以及一些幾何圖形,Shader下面包括幾個(gè)直接子類,分別是:
BitmapShader : 圖像渲染
LinearGradient : 線性漸變
RadialGradient : 環(huán)形漸變
SweepGradient : 掃描漸變---圍繞一個(gè)中心點(diǎn)掃描漸變就像電影里那種雷達(dá)掃描
ComposeShader : 組合渲染
Shader類的使用,都需要先構(gòu)建Shader對(duì)象,然后通過Paint的setShader方法設(shè)置渲染對(duì)象,然后設(shè)置渲染對(duì)象,然后再繪制時(shí)使用這個(gè)Paint對(duì)象即可。
簡(jiǎn)單的shader使用如下圖所示。分別使用了LinearGradient,RadialGradient和SweepGradient。
4 Bitmap
4.1 綜述
我們?cè)谇懊嬉呀?jīng)提到過,Canvas下面真正的內(nèi)存區(qū)域?qū)嶋H上是Bitmap。
而實(shí)際上,Bitmap的作用不止于此。
Bitmap是Android系統(tǒng)中的圖像處理的最重要類之一。它還可以獲取圖像文件信息,進(jìn)行圖像剪切、旋轉(zhuǎn)、縮放等操作,并可以指定格式保存圖像文件。
我們可以通過以下幾種不同的方法來從不同的來源中獲取一個(gè)Bitmap:
Resource : BitmapFactory.decodeResource()
File : BitmapFactory.decodeFile()
Byte Array : BitmapFactory.decodeByteArray()
Stream : BitmapFactory.decodeStream()
Bitmap的繪制很簡(jiǎn)單,只需要調(diào)用Canvas.drawBitmap(Bitmap,...)方法即可。
4.2 Bitmap的縮放和旋轉(zhuǎn)
我們有三種辦法可以對(duì)一個(gè)指定的位圖進(jìn)行縮放。
1 .使用Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)方法,將現(xiàn)有bitmap轉(zhuǎn)化為一個(gè)指定大小的bitmap
2 . 我們也可以使用BitmapFactory.Options,在創(chuàng)建位圖時(shí)就進(jìn)行縮放:
BitmapFactory.Options mOptions = new BitmapFactory.Options();
mOptions.inSampleSize = 4;
Bitmap mBitmap = BitmapFactory.decodeFile("/sdcard/image.jpg", options);
這段代碼將把位圖縮小到1/4.
3 .我們也可以使用Matrix 類來實(shí)現(xiàn)更靈活的縮放。
Matrix,顧名思義,是一個(gè)矩陣類,對(duì)于縮放,我們有兩種方法可以使用: preScale(float sx, float sy)和postScale(float sx, float sy).
這兩種方法都是將現(xiàn)有bitmap做了矩陣坐標(biāo)的變換。
不同的是,pre方法使用的變換公式是:M' = M * S(sx, sy)
而post方法使用的變換公式是:M‘ = S(sx, sy) * M。
例如如果我們使用:mirrorMatrix.preScale(-0.2f, 0.2f);
這個(gè)矩陣變換的結(jié)果是矩陣中所有的點(diǎn)做了左右顛倒,并且縮放了0.2倍
配置好Matrix之后,只需要調(diào)用Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) 方法,將設(shè)置好的Matrix 傳入,我們就可以生成一個(gè)縮放且顛倒的Bitmap。
位圖的旋轉(zhuǎn)和縮放類似。
使用Matrix 類來實(shí)現(xiàn)旋轉(zhuǎn)。
mirrorMatrix.preRotate(30);
實(shí)際上是執(zhí)行了M‘ = M * R(degrees)這個(gè)矩陣變換,最終的效果是位圖被向右旋轉(zhuǎn)了30°
Bitmap旋轉(zhuǎn)和縮放的效果如下圖所示。
4.3 Bitmap的常見問題
由于bitmap底層對(duì)應(yīng)了內(nèi)存的分配和使用,所以如果使用不當(dāng),有可能會(huì)導(dǎo)致內(nèi)存問題的產(chǎn)生,一般常見的內(nèi)存問題有兩大類。
4.3.1 內(nèi)存溢出
android系統(tǒng)中讀取位圖Bitmap時(shí).分給虛擬機(jī)中圖片的堆棧大小是有限的,所以如果堆棧分配過多的時(shí)候,就會(huì)導(dǎo)致內(nèi)存溢出。如下面這段代碼:
ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
for (int i = 0; i < 10; i++) {
Bitmap mConvertedBitmap = Bitmap.createBitmap(mBitmap, 0, 0,
mBitmap.getWidth(), mBitmap.getHeight(), mirrorMatrix, false);
bitmaps.add(mConvertedBitmap);
}
執(zhí)行這段代碼就會(huì)發(fā)生內(nèi)存溢出,導(dǎo)致應(yīng)用FC。只有十張普通大小的圖片,一旦使用不當(dāng),也會(huì)導(dǎo)致內(nèi)存溢出的發(fā)生。因此,我們一定要及時(shí)對(duì)不使用的bitmap調(diào)用Bitmap.recycle方法,防止內(nèi)存溢出的發(fā)生。
4.3.2 回收不當(dāng)
我們上一點(diǎn)中,需要及時(shí)對(duì)不使用的bitmap調(diào)用Bitmap.recycle方法,但是也需要注意的是,一定要保證bitmap在被recycle后,不要再使用這個(gè)bitmap。
否則將會(huì)發(fā)生Canvas: trying to use a recycled bitmap的異常被拋出,從而導(dǎo)致應(yīng)用FC。
由于Bitmap直接涉及到了內(nèi)存的操作,出問題的可能性較大,Google也給出了很多針對(duì)Bitmap的優(yōu)化手段:
Loading Large Bitmaps Efficiently:
http://developer.android.com/intl/zh-cn/training/displaying-bitmaps/load-bitmap.html
(上文的核心思想是加載壓縮后的圖片,主要就是通過設(shè)置options.inJustDecodeBounds = true,第一次加載圖片獲取圖片大小,根據(jù)圖片大小來設(shè)置壓縮參數(shù),第二次才真正加載圖片)
Caching Bitmaps:
http://developer.android.com/intl/zh-cn/training/displaying-bitmaps/cache-bitmap.html
(上文核心就是使用LruCache)
5 Typeface
Class Overview
The Typeface class specifies the typeface and intrinsic style of a font. This is used in the paint, along with optionally Paint settings like textSize, textSkewX, textScaleX to specify how text appears when drawn (and measured).
Typeface的很簡(jiǎn)單,主要就是一個(gè)字體的輔助類。一般情況下,我們就是使用字體文件創(chuàng)建一個(gè)Typeface,然后使用Typeface即可。
//Default Font
Typeface mType = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
mPaint.setTypeface(mType);
// Custom Font : /assets/fonts/abc.ttf
Typeface mType = Typeface.createFromAsset(getContext().getAssets(),"fonts/aaa.ttf");
mPaint.setTypeface(mType);
//Custom Font : /sdcard/abc.ttf
Typeface mType = Typeface.createFromFile("sdcard/abc.ttf");
mPaint.setTypeface(mType);
6 SurfaceView
我們前面提到,在對(duì)速度有較高的要求的應(yīng)用中,如一些實(shí)時(shí)性要求高的游戲中,或者攝像頭預(yù)覽、視頻播放來說,它們的UI都比較復(fù)雜,而且要求能夠進(jìn)行高效的繪制,這種情況下我們應(yīng)該使用surfaceView。
SurfaceView擁有獨(dú)立的繪圖表面,即它不與其宿主窗口共享同一個(gè)繪圖表面。由于擁有獨(dú)立的繪圖表面,因此SurfaceView的UI就可以在一個(gè)獨(dú)立的線程中進(jìn)行行繪制。又由于不占用主線程資源,SurfaceView一方面可以實(shí)現(xiàn)復(fù)雜而高效的UI,另一方面又不會(huì)導(dǎo)致用戶輸入得不到及時(shí)響應(yīng)。
對(duì)于那些具有SurfaceView的窗口來說,每一個(gè)SurfaceView在SurfaceFlinger服務(wù)中還對(duì)應(yīng)有一個(gè)獨(dú)立的Layer,用來單獨(dú)描述它的繪圖表面,以區(qū)別于它的宿主窗口的繪圖表面。
用來描述SurfaceView的Layer的Z軸位置是小于用來其宿主Activity窗口的Layer的Z軸位置的,但是前者會(huì)在后者的上面挖一個(gè)“洞”出來,以便它的UI可以對(duì)用戶可見。實(shí)際上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不過是在其宿主Activity窗口上設(shè)置了一塊透明區(qū)域。
說完前面的理論知識(shí),我們來看下SurfaceView是如何使用的。
典型的使用方法如下圖所示。
首先我們需要一個(gè)繼承自SurfaceView的類,實(shí)現(xiàn)一個(gè)帶Context參數(shù)的構(gòu)造函數(shù)
其次需要通過getHolder函數(shù)來獲取一個(gè)SurfaceHolder,它是一個(gè)接口,其作用就像一個(gè)關(guān)于Surface的監(jiān)聽器。提供訪問和控制SurfaceView背后的Surface 相關(guān)的,它通過三個(gè)回調(diào)方法,讓我們可以感知到Surface的創(chuàng)建、銷毀或者改變
我們實(shí)現(xiàn)SurfaceHolder.Callback下面的三個(gè)函數(shù)
最后,我們就可以在Surface被創(chuàng)建后,通過我們前面提過的方法lockCanvas來獲取到Canvas,從而在它上面作畫了。
最后,我們來詳細(xì)的展開說明一下SurfaceHolder中的幾個(gè)重要函數(shù):
abstract Canvas lockCanvas()
獲取一個(gè)Canvas對(duì)象,并鎖定。所得到的Canvas對(duì)象,其實(shí)就是Surface中一個(gè)成員。abstract Canvas lockCanvas(Rect dirty)
同上。但只鎖定dirty所指定的矩形區(qū)域,因此效率更高。abstract void unlockCanvasAndPost(Canvascanvas)
當(dāng)修改Surface中的數(shù)據(jù)完成后,釋放同步鎖,并提交改變。
注意,只有在這個(gè)函數(shù)被調(diào)用后,對(duì)Canvas所做的修改才能真正被顯示出來。
從顯示效果來說,SurfaceView和普通View并無明顯區(qū)別,但是響應(yīng)速度有大幅度提升。
7 Animation
最后,我們來了解一下Android中動(dòng)畫的使用方法。
目前android動(dòng)畫有三大類:
- View Animation(Tween Animation):補(bǔ)間動(dòng)畫,給出兩個(gè)關(guān)鍵幀,通過一些算法將給定屬性值在給定的時(shí)間內(nèi)在兩個(gè)關(guān)鍵幀間漸變。
- Drawable Animation(Frame Animation):幀動(dòng)畫,就像GIF圖片,通過一系列Drawable依次顯示來模擬動(dòng)畫的效果
- Property Animation :屬性動(dòng)畫,這個(gè)是在Android 3.0中才引進(jìn)的
我們將在后面依次介紹這三種動(dòng)畫。
7.1 View Animation
View animation只能應(yīng)用于View對(duì)象,而且只支持一部分屬性,如支持縮放旋轉(zhuǎn)而不支持背景顏色的改變。
而且對(duì)于View animation,它只是改變了View對(duì)象繪制的位置,而沒有改變View對(duì)象本身。比如,你有一個(gè)Button,坐標(biāo)(100,100),Width:200,Height:50,而你有一個(gè)動(dòng)畫使其變?yōu)閃idth:100,Height:100,你會(huì)發(fā)現(xiàn)動(dòng)畫過程中觸發(fā)按鈕點(diǎn)擊的區(qū)域仍是(100,100)-(300,150)。
View Animation就是一系列View形狀的變換,如大小的縮放,透明度的改變,位置的改變,動(dòng)畫的定義既可以用代碼定義也可以用XML定義。
可以給一個(gè)View同時(shí)設(shè)置多個(gè)動(dòng)畫,比如從透明至不透明的淡入效果,與從小到大的放大效果,這些動(dòng)畫可以同時(shí)進(jìn)行,也可以在一個(gè)完成之后開始另一個(gè)。
用XML定義的動(dòng)畫放在/res/anim/文件夾內(nèi)。
動(dòng)畫的定義既可以用代碼定義也可以用XML定義,因?yàn)楹笳吒鼮殪`活,因此建議用XML定義,我們?cè)诖艘惨詫W(xué)習(xí)XML定義的動(dòng)畫為主。
XML文件的根元素可以為<alpha>,<scale>,<translate>, <rotate> 元素或<set>(表示以上幾個(gè)動(dòng)畫的集合,如上面的第四個(gè)例子,set可以嵌套)。
默認(rèn)情況下,所有動(dòng)畫是同時(shí)進(jìn)行的,可以通過startOffset屬性設(shè)置 各個(gè)動(dòng)畫的開始偏移(開始時(shí)間)來達(dá)到動(dòng)畫順序播放的效果。
動(dòng)畫的播放邏輯很簡(jiǎn)單:
Animation an = AnimationUtils.loadAnimation(this, R.anim.anim);
imageview.startAnimation(an);
Load到指定的動(dòng)畫配置文件,然后對(duì)需要播放動(dòng)畫的View使用startAnimation即可。
7.2 Drawable Animation
Drawable Animation(Frame Animation):幀動(dòng)畫,就像GIF圖片,通過一系列Drawable依次顯示來模擬動(dòng)畫的效果。在XML中的定義方式如下:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/fish001" android:duration="150"/>
<item android:drawable="@drawable/fish002" android:duration="150"/>
<item android:drawable="@drawable/fish003" android:duration="150"/>
<item android:drawable="@drawable/fish004" android:duration="150"/>
</animation-list>
必須以<animation-list>為根元素,以<item>表示要輪換顯示的圖片,duration屬性表示各項(xiàng)顯示的時(shí)間。
代碼中使用也很簡(jiǎn)單:
ImageView imageview = (ImageView) findViewById(R.id.ImageView01);
imageview.setBackgroundResource(R.anim.animation);
AnimationDrawable mframeAnimation = (AnimationDrawable) imageview.getBackground();
mframeAnimation.start();
將一個(gè)ImageView調(diào)用setBackgroundResource,將動(dòng)畫設(shè)置為他的Background,生成一個(gè)AnimationDrawable 對(duì)象,然后使用它的start方法即可播放。
7.3 Property Animation
在Property Animation中,改變的是對(duì)象的實(shí)際屬性,如Button的縮放,Button的位置與大小屬性值都改變了。而且Property Animation不止可以應(yīng)用于View,還可以應(yīng)用于任何對(duì)象。Property Animation只是表示一個(gè)值在一段時(shí)間內(nèi)的改變,當(dāng)值改變時(shí)要做什么事情完全是你自己決定的。
在Property Animation中,可以對(duì)動(dòng)畫應(yīng)用以下屬性:
- Duration:動(dòng)畫的持續(xù)時(shí)間
- TimeInterpolation:屬性值的計(jì)算方式,如先快后慢
- TypeEvaluator:根據(jù)屬性的開始、結(jié)束值與TimeInterpolation計(jì)算出的因子計(jì)算出當(dāng)前時(shí)間的屬性值
- Repeat Count and behavoir:重復(fù)次數(shù)與方式,如播放3次、5次、無限循環(huán),可以此動(dòng)畫一直重復(fù),或播放完時(shí)再反向播放
- Animation sets:動(dòng)畫集合,即可以同時(shí)對(duì)一個(gè)對(duì)象應(yīng)用幾個(gè)動(dòng)畫,這些動(dòng)畫可以同時(shí)播放也可以對(duì)不同動(dòng)畫設(shè)置不同開始偏移
- Frame refreash delay:多少時(shí)間刷新一次,即每隔多少時(shí)間計(jì)算一次屬性值,默認(rèn)為10ms,最終刷新時(shí)間還受系統(tǒng)進(jìn)程調(diào)度與硬件的影響
一般可以設(shè)置的屬性如下:
1)translationX 和 translationY:這兩個(gè)屬性控制了View所處的位置,它們的值是由layout容器設(shè)置的,是相對(duì)于坐標(biāo)原點(diǎn)(0,0左上角)的一個(gè)偏移量。
2)rotation, rotationX 和 rotationY:控制View繞著軸點(diǎn)(pivotX和pivotY)旋轉(zhuǎn)。
3)scaleX 和 scaleY:控制View基于pivotX和pivotY的縮放。
4)pivotX 和 pivotY:旋轉(zhuǎn)的軸點(diǎn)和縮放的基準(zhǔn)點(diǎn),默認(rèn)是View的中心點(diǎn)。
5)x 和 y:描述了view在其父容器中的最終位置,是左上角左標(biāo)和偏移量(translationX,translationY)的和。
6)aplha:透明度,1是完全不透明,0是完全透明。
屬性動(dòng)畫有多種設(shè)置方式,我們逐一說明.
7.3.1 XML定義
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:duration="2000"
android:propertyName="scaleX"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="1.0"
android:valueTo="2.0" >
</objectAnimator>
<objectAnimator
android:duration="2000"
android:propertyName="scaleY"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="1.0"
android:valueTo="2.0" >
</objectAnimator>
</set>
用一個(gè)set可以包裹多個(gè)objectAnimator ,可以使用android:ordering屬性來指定這些動(dòng)畫是同時(shí)播放還是順序播放.xml推薦放在res下的animator目錄.
在XML中要利用objectAnimator來定義我們的某個(gè)屬性的效果,所以:
1)propertyName 當(dāng)然是必須的,理論上這個(gè)屬性的值可以是對(duì)象中的任何有g(shù)et/set方法的屬性,不過我們這里是對(duì)View來說,所以一般而言,就是上面新加的那幾種新屬性了。
2)duration 持續(xù)時(shí)間
3)valueFrom 和 valueTo:這就是動(dòng)畫開始和結(jié)束時(shí),這個(gè)屬性相對(duì)應(yīng)的值了,如果我們是縮放的話,當(dāng)然就是倍數(shù)了,如果是平移(Translation)的話,那么就是距離了。
4)repeatCount 和 repeatMode:這兩個(gè)和View Animation 中是一樣的,就不多說了。
xml定義完之后在代碼中使用即可:
TextView textView = (TextView) findViewById(R.id.hello_text);
final AnimatorSet objectAnimator = (AnimatorSet) AnimatorInflater
.loadAnimator(this, R.animator.scalex);
objectAnimator.setTarget(textView);
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
objectAnimator.start();
}
});
調(diào)用AnimatorSet 的start就是開始播放動(dòng)畫了.
7.3.2 代碼直接定義動(dòng)畫
7.3.2.1 直接創(chuàng)建AnimatorSet對(duì)象
Button button = (Button) findViewById(R.id.button1);
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(button, "alpha", 1, 0, 1),
ObjectAnimator.ofFloat(button, "translationX", 0f, 400f, 0f),
ObjectAnimator.ofFloat(button, "rotation", 0, 180, 360));
animatorSet.setDuration(1000);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
animatorSet.start();
}
});
通過playTogether將三種動(dòng)畫同時(shí)播放,ofFloat必須寫正確的屬性名稱.
7.3.2.2 使用PropertyValuesHolder對(duì)象創(chuàng)建
Button button2 = (Button) findViewById(R.id.button2);
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(
"translationX", 0f, 300f, 0f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(
"translationY", 0f, 300f, 0f);
final ValueAnimator translateAnimator = ObjectAnimator
.ofPropertyValuesHolder(button2, pvhX, pvhY);
translateAnimator.setDuration(2000);
translateAnimator.setInterpolator(new BounceInterpolator());
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
translateAnimator.start();
}
});
上面除了設(shè)置動(dòng)畫,還有設(shè)置了插值器(TimeInterpolator),也就是如下一句:
translateAnimator.setInterpolator(new BounceInterpolator());
插值器的意思就是對(duì)動(dòng)畫的播放進(jìn)行干預(yù),android提供了九個(gè)預(yù)置的插值器供我們使用:
1)AccelerateDecelerateInterpolator:先加速再減速。
2)AccelerateInterpolator:一直加速。
3)AnticipateInterpolator: 反向 ,先向相反方向改變一段再加速播放
4)AnticipateOvershootInterpolator: 反向加回彈,先向相反方向改變,再加速播放,會(huì)超出目的值然后緩慢移動(dòng)至目的值
5)BounceInterpolator: 跳躍,快到目的值時(shí)值會(huì)跳躍,最后像個(gè)小球彈幾下。
6)CycleInterpolator:循環(huán),動(dòng)畫循環(huán)一定次數(shù),值的改變?yōu)橐徽液瘮?shù):Math.sin(2 * mCycles * Math.PI * input)
7)DecelerateInterpolator:一直減速。
8)LinearInterpolator:線性,這個(gè)就是我們上面講到的很均勻的了。
9)OvershootInterpolator:到了終點(diǎn)之后,超過一點(diǎn),再往回走。有個(gè)參數(shù)可以定義,超過的力度。
使用起來很方便,直接給ValueAnimator 實(shí)例設(shè)置setInterpolator即可.
7.3.2.3 使用ViewPropertyAnimator對(duì)象創(chuàng)建
Button button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
v.animate().translationX(100f).alpha(0).setInterpolator(new CycleInterpolator((float) 0.5))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
final Button button = (Button) findViewById(R.id.button3);
button.animate().alpha(1).translationX(0f).setInterpolator(new OvershootInterpolator())
.start();
}
}).start();
}
});
先利用view的animate() 方法返回一個(gè)ViewPropertyAnimator對(duì)象,然后調(diào)用其translationX,alpha等方法.
上面也有一個(gè)額外的點(diǎn),就是setListener方法來增加了一個(gè)AnimatorListenerAdapter.
AnimatorListenerAdapter可以實(shí)現(xiàn)諸如onAnimationStart onAnimationEnd onAnimationCancel等方法在特定的時(shí)間點(diǎn)來調(diào)用特定的代碼.
諸如上面的代碼,就是在動(dòng)畫播放之后,繼續(xù)再播放一段動(dòng)畫.
7.4 一些額外的
7.4.1 ViewFilpper
在app的開發(fā)中,我們有時(shí)需要在不同的View間切換,實(shí)現(xiàn)幻燈片播放一樣的效果,這里我們可以使用系統(tǒng)提供的ViewFilpper類。
ViewFilpper 是Android官方提供的一個(gè)View容器類,繼承于ViewAnimator類,用于實(shí)現(xiàn)頁面切換,也可以設(shè)定時(shí)間間隔,讓它自動(dòng)播放。
ViewFilpper的Layout里面可以放置多個(gè)View。
通過下面兩個(gè)屬性,可以設(shè)置ViewFilpper中View切換時(shí)的進(jìn)入和退出時(shí)的動(dòng)畫。
android:inAnimation="@anim/push_top_in"
android:outAnimation="@anim/push_down_out“
在代碼中,我們可以調(diào)用startFlipping()來開始自動(dòng)播放,也可以通過showPrevious和showNext來控制播放前一張或后一張.
7.4.2 overridePendingTransition
前面我們提到了View間切換可以使用ViewFilpper,而在兩個(gè) Activity 切換時(shí),我們?nèi)绾尾シ艅?dòng)畫呢?
Android同樣給我們提供了方便的辦法來播放這種動(dòng)畫,使用overridePendingTransition()函數(shù)。
overridePendingTransition(R.anim.push_top_in, R.anim.push_down_out);
第一個(gè)參數(shù)是進(jìn)入的動(dòng)畫,第二個(gè)參數(shù)是退出的動(dòng)畫。
注意這個(gè)函數(shù)必須在 StartActivity() 或 finish() 或者onPause()之后立即調(diào)用。