1.前言
安卓框架提供了一組2D繪圖的APIs鬓照,允許在畫布上渲染自定義圖形或者修改已存在的View來定義外觀和感覺涮母。繪制2D圖形通常有兩種途徑:
a.給布局中的View對象繪制圖形和動(dòng)畫槐秧。由系統(tǒng)的視圖層次結(jié)構(gòu)處理繪制過程光酣,你只需定義視圖內(nèi)的圖形。
b.直接將圖形繪制到畫布上犀概。你自己調(diào)用適當(dāng)類的 onDraw() 方法(傳入畫布)或者Canvas對象的 draw...() 方法(就像 drawPicture() 方法)敢靡,這樣也可以控制任何動(dòng)畫挂滓。
選項(xiàng)“a”,當(dāng)繪制的是不需要?jiǎng)討B(tài)變化的簡單圖形和非性能密集型游戲的一部分時(shí)啸胧,在視圖上繪制是最好的選擇赶站。例如,顯示靜態(tài)圖形或預(yù)定義的動(dòng)畫纺念。詳細(xì)信息看圖形這一章節(jié)贝椿。
選項(xiàng)“b”,當(dāng)應(yīng)用需要經(jīng)常重繪時(shí)陷谱,在畫布上繪制是較好的選擇烙博。例如,電子游戲等烟逊。有兩種方式去實(shí)現(xiàn):
- 與UI Activity同一線程時(shí)渣窜,給布局創(chuàng)建自定義視圖組件,需要調(diào)用 invalidate() 方法和處理 onDraw() 方法的回調(diào)焙格。
- 一個(gè)單獨(dú)的線程時(shí)图毕,管理一個(gè)SurfaceView,在畫布上以線程支持的最快速度繪制(不需要請求 invalidate())眷唉。
2.使用畫布繪制
寫程序時(shí)予颤,若希望執(zhí)行專門的繪圖和/或控制圖形的動(dòng)畫,應(yīng)該在畫布上繪制冬阳。畫布只是表面的封裝蛤虐,負(fù)責(zé)所有 draw...() 方法的調(diào)用,圖形傳遞給實(shí)際的Surface上肝陪,擺放在窗口中的底層的位圖驳庭。
如果在 onDraw() 回調(diào)方法內(nèi)繪制,調(diào)用提供的Canvas的 draw...() 方法即可氯窍。處理SurfaceView對象時(shí)饲常,通過 SurfaceHolder.lockCanvas() 方法獲取Canvas對象。(這兩種情況在下面的章節(jié)中都有討論)如果需要?jiǎng)?chuàng)建新的Canvas對象狼讨,必須定義實(shí)際執(zhí)行繪制的Bitmap對象贝淤。畫布總是需要位圖配合,創(chuàng)建新畫布如下:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
現(xiàn)在畫布將在給定的位圖上繪制政供,繪制完后播聪,通過 drawBitmap(Bitmap,...) 方法可將位圖帶給另外畫布。建議在 View.onDraw() 或 SurfaceHolder.lockCanvas() 方法提供的畫布上繪制最終圖形(詳見下面的章節(jié))布隔。
2.1.繪制在View上
如果你的應(yīng)用不需要大量的處理或好高的幀速率(也許是象棋游戲离陶,貪吃蛇游戲或者緩慢的動(dòng)畫應(yīng)用),應(yīng)該創(chuàng)建自定義視圖組件和在 View.onDraw() 方法內(nèi)用畫布繪圖衅檀。這樣做方便的是招刨,安卓系統(tǒng)將提供預(yù)定義的Canvas對象來調(diào)用 draw...() 方法。
首先哀军,繼承View類(或其子類)和定義 onDraw() 回調(diào)方法计济。當(dāng)視圖繪制自己時(shí),安卓系統(tǒng)將調(diào)用這個(gè)方法排苍,通過傳入的Canvas對象執(zhí)行所有的繪制操作沦寂。
安卓系統(tǒng)只會(huì)在必要時(shí)調(diào)用 onDraw() 方法。每當(dāng)應(yīng)用準(zhǔn)備好繪制時(shí)淘衙,調(diào)用 invalidate() 方法廢除當(dāng)前的視圖传藏,同時(shí)告知安卓系統(tǒng)調(diào)用 onDraw() 方法(不能保證回調(diào)是瞬時(shí)的)。
在視圖組件的 onDraw() 方法內(nèi)彤守,使用提供的Canvas對象的各種 draw...() 方法或其它類的 draw() 方法(以提供的Canvas對象為參數(shù))完成所有繪圖毯侦。一旦 onDraw() 方法完成,安卓系統(tǒng)將使用畫布繪制位圖具垫。
注意:為了從非主線程刷新界面侈离,必須調(diào)用 postInvalidate() 方法。
關(guān)于擴(kuò)展View類的更多信息筝蚕,閱讀 read Building Custom Components卦碾。
2.2.繪制在SurfaceView上
SurfaceView是View的一個(gè)專注于繪圖的子類铺坞,目的是在應(yīng)用的子線程中提供繪圖功能,那樣應(yīng)用不需要等待系統(tǒng)視圖結(jié)構(gòu)的繪制完成洲胖。反而济榨,與SurfaceView相關(guān)聯(lián)的子線程可以按照自己的頻率在畫布上繪制。
首先绿映,需要?jiǎng)?chuàng)建一個(gè)類繼承SurfaceView擒滑,同時(shí)實(shí)現(xiàn)SurfaceHolder.Callback接口。它可以提供Surface的底層信息叉弦,例如丐一,什么時(shí)候被創(chuàng)建、被改變或者被銷毀淹冰。這些信息很重要库车,可以知道何時(shí)開始繪圖,是否需要根據(jù)新的Surface屬性進(jìn)行調(diào)整榄棵,何時(shí)停止繪圖凝颇,以及殺死某些任務(wù)。在SurfaceView類內(nèi)部定義子線程疹鳄,以便在你的畫布上執(zhí)行所有的繪圖過程拧略。
對于Surface的操作應(yīng)該通過SurfaceHolder而不是直接處理。當(dāng)SurfaceView被初始化后瘪弓,調(diào)用 getHolder() 方法獲取SurfaceHolder垫蛆。通過 addCallback() 方法(參數(shù)為this)可以將回調(diào)對象傳入SurfaceHolder以獲取通知,而被回調(diào)的方法需在SurfaceView類中重寫腺怯。
為了在子線程中通過畫布繪制袱饭,需要將SurfaceHolder對象傳進(jìn)線程,并調(diào)用它的 lockCanvas() 方法獲取畫布呛占,有了畫布就可以進(jìn)行必要的繪制操作了虑乖。畫完后,調(diào)用 unlockCanvasAndPost() 方法解鎖和傳遞畫布對象晾虑,這樣疹味,內(nèi)容才會(huì)顯示到畫布上。每當(dāng)要重繪時(shí)帜篇,先鎖定畫布再解鎖畫布糙捺,代碼看這篇文章。
注意:每次從SurfaceHolder獲取畫布笙隙,畫布之前的狀態(tài)將會(huì)被保留洪灯。為了正確展示動(dòng)畫,你必須重繪整個(gè)Surface竟痰。比如签钩,調(diào)用 drawColor() 方法填充一種顏色或者調(diào)用 drawBitmap() 方法設(shè)置一個(gè)背景圖像來清除畫布之前的狀態(tài)掏呼。否則,會(huì)在畫布上看到之前繪制過的痕跡边臼。
3.圖形
安卓提供了一個(gè)自定義2D圖形庫哄尔,用于繪制形狀和圖像假消。這些在兩個(gè)維度上繪制的公共類放在 android.graphics.drawable 包中柠并。
本文討論使用Drawable對象繪制圖形的基本知識(shí)和如何使用Drawable子類。關(guān)于使用圖形完成幀動(dòng)畫富拗,詳見 Drawable Animation臼予。
圖形通常指可以繪制的東西,它的子類定義了各種具體的可繪圖形啃沪,包括BitmapDrawable粘拾,ShapeDrawable,PictureDrawable创千,LayerDrawable等缰雇。當(dāng)然,也可以繼承這些類追驴,來實(shí)現(xiàn)自己想要的械哟、具有獨(dú)特行為的Drawable對象。
有三種方式定義和實(shí)例化Drawable對象:使用保存在項(xiàng)目資源中的圖像殿雪;使用XML文件定義Drawable屬性暇咆;使用正常的類構(gòu)造函數(shù)。下面丙曙,將分別討論前兩種技術(shù)(第三種就是代碼的調(diào)用爸业,功能最全也不難)。
3.1.使用圖像資源創(chuàng)建
向應(yīng)用程序添加圖形的簡單方法是引用項(xiàng)目資源中的圖像文件亏镰,支持的文件類型有:PNG(首選)扯旷,JPG(可接受),GIF(不建議)索抓。這種技術(shù)顯然更適合應(yīng)用程序圖標(biāo)钧忽、徽標(biāo)或其它情況(如在游戲中使用)。
使用圖像資源纸兔,僅僅需要將文件添加到你項(xiàng)目的 res/drawable/ 目錄下惰瓜,再從代碼和XML布局中引用。無論哪種方式汉矿,都涉及到使用資源ID崎坊,一種沒有文件類型擴(kuò)展名的文件名(例如,my_image.png 用my_image引用)洲拇。
注意:放在 res/drawable/ 目錄下的圖像資源可能會(huì)被aapt工具在編譯過程中使用圖像無損壓縮進(jìn)行自動(dòng)優(yōu)化奈揍,比如曲尸,一個(gè)真的不需要超過256種顏色的PNG可能會(huì)被顏色調(diào)色板轉(zhuǎn)換成8位的PNG。在圖像質(zhì)量不變的情況下男翰,可以使用較少的內(nèi)存另患。因此,需要認(rèn)識(shí)到蛾绎,這個(gè)目錄下的圖像二進(jìn)制文件在編譯的過程中會(huì)改變昆箕。如果計(jì)劃用位流讀取圖像轉(zhuǎn)換成位圖,最好將圖像放到 res/raw/ 目錄下租冠,避免被優(yōu)化鹏倘。
示例代碼(使用圖形資源創(chuàng)建ImageView并添加到布局中)
LinearLayout mLinearLayout;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a LinearLayout in which to add the ImageView
mLinearLayout = new LinearLayout(this);
// Instantiate an ImageView and define its properties
ImageView i = new ImageView(this);
i.setImageResource(R.drawable.my_image);
i.setAdjustViewBounds(true); // set the ImageView bounds to match the Drawable's dimensions
i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
// Add the ImageView to the layout and set the layout as the content view
mLinearLayout.addView(i);
setContentView(mLinearLayout);
}
有些情況下,可能想要用Drawable對象來處理圖像資源顽爹。那么纤泵,通過資源創(chuàng)建Drawable對象如下:
Resources res = mContext.getResources();
Drawable myImage = res.getDrawable(R.drawable.my_image);
項(xiàng)目中每個(gè)獨(dú)特的資源,不管實(shí)例化多少個(gè)不同的對象镜粤,只能維持一種狀態(tài)捏题。例如,對同一個(gè)圖像資源實(shí)例化兩個(gè)Drawable對象肉渴,改變其中一個(gè)對象的一個(gè)屬性(拿透明度來說)公荧,將會(huì)影響另一個(gè)。所以黄虱,處理一個(gè)對象的多個(gè)資源時(shí)稚矿,使用補(bǔ)間動(dòng)畫來改變圖形比直接操作要好。
示例XML(在XML布局中給ImageView添加圖形資源)
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="#55ff0000"
android:src="@drawable/my_image"/>
關(guān)于使用項(xiàng)目資源的詳細(xì)信息捻浦,閱讀 Resources and Assets晤揣。
3.2.使用XML資源創(chuàng)建
若對安卓開發(fā)用戶界面的原則熟悉,應(yīng)該清楚在XML中定義對象所固有的能力和靈活性朱灿,這種理念從View貫穿到Drawable昧识。如果想創(chuàng)建的Drawable對象不依賴于應(yīng)用程序代碼或者用戶交互,那么在XML中定義是個(gè)好的選擇盗扒。即使期望圖形隨著用戶的使用而改變屬性跪楞,你也應(yīng)該認(rèn)識(shí)到在XML中定義對象可以隨時(shí)修改初始化時(shí)的屬性。
一旦在XML中定義Drawable對象侣灶,文件需保存到項(xiàng)目的 res/drawable/ 目錄下甸祭。然后調(diào)用 Resources.getDrawable() 方法,根據(jù)XML文件的資源ID獲取和初始化對象褥影。
任何支持 inflate() 方法的Drawable子類能夠被XML定義和被應(yīng)用初始化池户,它們特有的XML屬性能夠找到對應(yīng)的對象屬性(詳見類參考)。
示例
// TransitionDrawable在XML中的使用
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/image_expand">
<item android:drawable="@drawable/image_collapse">
</transition>
// 實(shí)例化TransitionDrawable并設(shè)置為ImageView的內(nèi)容
Resources res = mContext.getResources();
TransitionDrawable transition = (TransitionDrawable)res.getDrawable(R.drawable.expand_collapse);
ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);
// 運(yùn)行變換一秒鐘
transition.startTransition(1000);
4.形狀圖片
當(dāng)想要?jiǎng)討B(tài)地繪制兩個(gè)維度的圖形時(shí),ShapeDrawable對象將滿足需求校焦,可以通過編程的方式繪制原始形狀和定義任何樣式赊抖。
ShapeDrawable繼承自Drawable類,可以被當(dāng)作Drawable對象使用寨典,比如氛雪,調(diào)用 setBackgroundDrawable() 方法給視圖設(shè)置背景。當(dāng)然也可以給自定義視圖繪制自己的形狀耸成,但記得添加到自己的布局中报亩。因?yàn)镾hapeDrawable對象有自己的 draw() 方法,所以在View子類的 View.onDraw() 方法中調(diào)用ShapeDrawable對象的 draw() 方法墓猎。下面是對View類最基本的擴(kuò)展捆昏,繪制一個(gè)ShapeDrawable:
public class CustomDrawableView extends View {
private ShapeDrawable mDrawable;
public CustomDrawableView(Context context) {
super(context);
int x = 10;
int y = 10;
int width = 300;
int height = 50;
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
}
}
在構(gòu)造方法中赚楚,ShapeDrawable對象被定義為橢圓形毙沾,然后給了顏色和大小。如果不設(shè)置形狀宠页,將不會(huì)繪制左胞;如果不設(shè)置顏色,將默認(rèn)黑色举户。
在自定義視圖中可以繪制任何想要的樣子烤宙。我們可以將上面示例的形狀用代碼繪制到Activity中:
CustomDrawableView mCustomDrawableView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView);
}
若要通過XML布局自定義圖形而不是在Activity中設(shè)置,CustomDrawableView類必須重寫 View(Context, AttributeSet) 構(gòu)造函數(shù)俭嘁,因?yàn)閺腦ML實(shí)例化View對象時(shí)將會(huì)調(diào)用躺枕。接著將CustomDrawableView元素添加到Activity的XML布局中,如下:
<com.example.shapedrawable.CustomDrawableView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
ShapeDrawable類(像 android.graphics.drawable 包中其它Drawable類型一樣)允許通過公開的方法定義各種屬性供填,包括alpha拐云、濾色器、震動(dòng)近她、不透明度和顏色叉瘩。
使用XML也可以定義原始形狀,詳細(xì)信息參考 Drawable Resources文檔中ShapeDrawable章節(jié)粘捎。
5.可伸縮圖片
NinePatchDrawable是一個(gè)可伸縮的位圖圖像薇缅,在標(biāo)準(zhǔn)PNG圖像的基礎(chǔ)上加了1像素寬的邊框。當(dāng)它作為背景時(shí)攒磨,安卓會(huì)自動(dòng)調(diào)整大小以適應(yīng)視圖中的內(nèi)容泳桦。它必須以.9.png為擴(kuò)展名保存在項(xiàng)目的 res/drawable/ 目錄下。
NinePatch圖分為兩個(gè)部分娩缰,左灸撰、上為定義拉伸區(qū),右、下為定義內(nèi)邊距(里面是內(nèi)容區(qū))梧奢,如下圖:
將它作為Button的背景圖時(shí)狱掂,實(shí)際效果如下:
6.矢量圖片
VectorDrawable對象由定義在XML文件中的一系列點(diǎn)、線亲轨、曲線和相關(guān)的顏色信息組成趋惨。安卓5.0(API 21)開始,VectorDrawable和AnimatedVectorDrawable這兩個(gè)類支持矢量圖形作為圖形資源惦蚊。之前若想使用器虾,支持庫23.2或更高也提供矢量圖形和動(dòng)態(tài)矢量圖形的支持。
關(guān)于使用矢量圖形系統(tǒng)APIs或矢量圖形支持庫的更多信息蹦锋,前往 Vector Drawable兆沙。