Android畫布和圖形繪制---Canvas and Drawables(一)
注:本文譯自:http://developer.android.com/guide/topics/graphics/2d-graphics.html
Android框架API提供了一組2D描畫API,使用這些API能夠在一個畫布(canvas)上渲染自己的定制圖形,也能夠修改那些既存的View對象,來定制它們的外觀和視覺效果。在繪制2D圖形時,通常要使用以下兩種方法中的一種:
1. 把圖形或動畫繪制到布局中的一個View對象中。在這種方式中,圖形的繪制是由系統通常的繪制View層次數據的過程來處理的---只需簡單的定義要繪制到View對象內的圖形即可。
2. 把圖形直接繪制在一個畫布對象上(Canvas對象)。這種方法,要親自調用相應類的onDraw()方法(把圖形傳遞給Canvas對象),或者調用Canvas對象的一個draw…()方法(如drawPicture())。在這個過程中,還可以控制任何動畫。
當想要把不需要動態變化和沒有游戲性能要求的一個簡單的圖形繪制到View對象時,方法一是最好的選擇。例如,在想要在一個靜態的應用程序中,顯示一個靜態圖形或預定義動畫時,就應該用方法1把圖形繪制到一個View對象中。
當應用程序需要經常重新繪制自己的時候,使用方法2把圖形繪制到Canvas中,是一個比較好的選擇。像視頻游戲這樣的應用程序,就應該在它們自己的Canvas對象上繪制圖形。但是,有更多的方法來完成繪制任務:
1. 在與UI的Activity相同的線程中,創建布局中一個定制的View對象組件,就先要調用invalidate()方法,然后處理onDraw()回調方法;
2. 在一個獨立的線程中,管理着SurfaceView對象,並且使用線程來執行把圖形繪制到Canvas對象上的任務(不需要請求invalidate()方法)。
用Canvas對象來繪制圖形(Draw with a Canvas)
當要編寫專業的繪圖或控制圖形動畫的應用程序時,應該使用Canvas對象來盡心繪制操作。Canvas用一個虛擬的平面來工作,以便把圖形繪制在實際的表面上---它持有所有的用draw開頭的方法調用。通過Canvas對象,實際上是執行一個底層的位圖繪制處理,這個位圖被放置到窗口中。
在onDraw()回調方法的繪制事件中,會提供一個Canvas對象,並且只需要把要繪制的內容交給Canvas對象就可以了。在處理SurfaceView對象時,還可以從SurfaceHolder.lockCanvas()方法來獲取一個Canvas對象。但是,如果需要創建一個新的Canvas對象,那么就必須在實際執行繪制處理的Canvas對象上定義Bitmap對象。對於Canvas對象來說,這個Bitmap對象是始終必須的,應該像以下示例這樣建立一個新的Canvas對象:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
現在就可以在被定義的Bitmap對象上繪圖了。在Canvas對象上繪制圖形之后,能夠用Canvas.drawBitmap(Bitmap, …)的一個方法,把該Bitmap對象繪制到另一個Canvas對象中。通過View.onDraw()方法或SufaceHolder.lockCanvas()方法提供的Canvas對象來完成最終的圖形繪制處理是被推薦的。
Canvas類有可以使用的自己的一組繪圖方法,如drawBitmap(…)、drawRect(…)、drawText(…)等。還可以使用其他的有draw()方法類。例如,可能想要把某些Drawable對象放到Canvas對象上。Drawable類就有帶有Canvas對象作為參數的draw()方法。
Android畫布和圖形繪制---Canvas and Drawables(二)
注:本文譯自:http://developer.android.com/guide/topics/graphics/2d-graphics.html
在View對象上繪圖
如果應用程序不需要大量的圖形處理或很高的幀速率(如一個棋類游戲、Snake游戲或另外的慢動畫類應用程序),那么就應該考慮創建一個定制的View組件,並且用該組件的View.onDraw()方法的Canvas參數來進行圖形繪制。這么做最大的方便是,Android框架會提供一個預定義的Canvas對象,該對象用來放置繪制圖形的調用。
從繼承View類(或其子類)開始,並定義onDraw()回調方法。系統會調用該方法來完成View對象自己的繪制請求。這也是通過Canvas對象來執行所有的圖形繪制調用的地方,這個Canvas對象是由onDraw()回調方法傳入的。
Android框架只在必要的時候才會調用onDraw()方法,每次請求應用程序准備完成圖形繪制任務時,必須通過調用invalidate()方法讓該View對象失效。這表明可以在該View對象上進行圖形繪制處理了,然后Android系統會調用該View對象的onDraw()方(盡管不保證該回調方法會立即被調用)。
在定制的View組件的onDraw()方法內部,使用給定的Canvas對象來完成所有的圖形繪制處理(如Canvas.draw…()方法或把該Canvas對象作為參數傳遞給其他類的draw()方法)。一旦onDraw()方法被執行完成,Android框架就會使用這個Canvas對象來繪制一個有系統處理的Bitmap對象。
注意:為了在一個線程中而不是主Activity的線程中發出一個失效請求,必須調用postInvalidate()。
有關繼承View類的更多信息,請閱讀創建定制化的組件(http://developer.android.com/guide/topics/ui/custom-components.html)
示例程序,請看Snake游戲,它在SDK示例代碼文件:/samples/Snake/。
在SurfaceView對象上繪圖
SurfaceView對象是一個特殊的View類的子類,它在View層次樹內提供了一個專用的圖形繪制平面。這個圖形繪制表面的主要目的是給應用程序提供一個輔助線程,以便應用程序不需要等待完成對系統的View層次樹的繪制。相反,引用SurfaceView對象的輔助線程能夠按照自己的節奏,把自己繪制在Canvas對象上。
首先需要創建一個繼承SurfaceView類的子類。該子類還應該實現SurfaceHolder.Callback類,它是一個能夠通知底層Surface類所發生的信息的接口。如Surface的創建、變化或銷毀等。這些事件對於了解什么時候能夠開始繪制圖形、是否需要基於新的表面屬性來進行調整、以及什么時候終止圖形繪制和殺死某些任務,是至關重要的。在SurfaceView子類的內部也是定義輔助線程類的好地方,它會執行所有的把圖形繪制到Canvas對象上的處理。
不要直接處理Surface對象,應該通過SurfaceHolder對象來處理它。因此,在SurfaceView子類被初始化的時候,要通過調用getHolder()方法來獲得SurfaceHolder對象。然后應該通過調用addCallback()方法,來通知SurfaceHolder對象,它所能夠接收SurfaceHolder回調對象(SurfaceHolder.Callback),然后再重寫SurfaceView子類內部的每個SurfaceHolder.CallBacke方法。
為了能夠在輔助線程內把圖形繪制到Surface對象的Canvas對象上,必須把SurfaceHondler對象和用lockCanvas()方法獲取的Canvas對象傳遞給輔助線程。現在就能夠用給定的SurfaceHolder對象和Canvas對象開始圖形繪制工作了,一旦用該Canvas對象完成了圖形繪制任務,就要調用unlockCanvasAndPost()方法,把繪圖用Canvas對象傳遞給該方法。現在Surface對象就會離開繪制圖形的Canvas對象。每次想要重新繪制圖形時,都要執行這個鎖定和解鎖的過程。
注意:每個通過SurfaceHolder對象獲取的Canvas對象,它之前的狀態都會被保留。為了正確的處理該圖形,必須重新繪制整個Surface對象。例如,能夠清除之前用drawColor()方法填充在Canvas對象中的顏色,或者是用drawBitmap()方法設置的背景圖片。否則,就會看到之前被執行的圖形繪制的軌跡。
示例應用程序,請看Lunar Lander游戲,它在SDK的示例文件夾:/samples/LunarLander/。
Android畫布和圖形繪制---Canvas and Drawables(三)
本文譯自:http://developer.android.com/guide/topics/graphics/2d-graphics.html
圖形繪制
Android為繪制圖形和圖片提供了一個定制的2D圖形類庫。android.graphics.drawable包中能夠找到用於繪制二維圖形的共同的類。
本文討論使用Drawable對象來繪制圖形的基礎知識,以及如何使用Drawable類的子類。關於使用Drawable對象來繪制幀動畫的信息,請看繪制動畫文檔(http://developer.android.com/guide/topics/graphics/drawable-animation.html)
Drawable對象是對圖形繪制的一般化抽象,你會發現很多用於繪制特殊類型圖形的Drawable類的子類,包括BitmapDrawable、ShapDrawable、PictureDrawable、LayerDrawable等。當然,你也可以繼承這些類,來定義自己的具有獨特行為的Drawable對象。
有三種方法來定義和初始化一個Drawable對象:1.使用保存在項目資源中的一個圖片;2.使用定義Drawable對象屬性的XML文件;3.使用普通的類構造器。以下我們將重點討論前兩種技術(使用構造器技術對於開發者來說並不陌生)。
從資源圖片中創建一個Drawable對象
通過引用項目資源中的一個圖片文件,把圖形添加到應用程序中是一種簡單的方法。支持的圖片格式包括:PNG(推薦格式)、JPG(可接受格式)、GIF(不推薦格式)。這種技術主要用於應用的圖標、Logo或游戲中所使用的那些圖形。
要使用圖片資源,只需把圖片添加到項目的res/drawable/目錄中。應用程序代碼或XML布局會從那兒來引用這些圖片。無論在那兒使用圖片,都要使用該圖片資源的ID,這個ID是不帶文件類型擴展名的文件名(如:my_image指向了my_image.png文件)。
注意:被放置在res/drawable/目錄下的圖片資源,在編譯期間,能夠被aapt工具自動的優化壓縮成無損的圖片。例如,一個不多於256色的真彩色PNG圖片,能夠被轉換成一個帶有調色板的8位PNG圖片。這樣就獲得同等品質的圖片,但卻需要更少的內存。因此,要了解放置在這個目錄下的圖片,在編譯期間的這種改變。如果為了把圖片轉換成一個位圖,而計划用位流的形式來讀取一張圖片,就要把該圖片放到res/raw/文件夾中,這個文件夾中的圖片不會被優化。
示例代碼
以下代碼片段演示了如何創建一個使用繪制資源圖片的ImageView對象,並把該對象添加到布局中:
LinearLayout mLinearLayout;
protectedvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// Create a LinearLayout in which to add the ImageView
mLinearLayout =newLinearLayout(this);
// Instantiate an ImageView and define its properties
ImageView i =newImageView(this);
i.setImageResource(R.drawable.my_image);
i.setAdjustViewBounds(true);// set the ImageView bounds to match the Drawable's dimensions
i.setLayoutParams(newGallery.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對象來處理,就要像下面代碼這樣來創建一個Drawable對象:
Resources res = mContext.getResources();
Drawable myImage = res.getDrawable(R.drawable.my_image);
注意:在項目中的每種資源只能保持一種狀態,不管實例化了多少該對象。例如:如果用同一個圖片資源來實例化了兩個Drawable對象,那么只要改變了其中一個Drawable對象一個屬性(如,透明度),另一個也會受到影響。因此在處理一個圖片資源的多個實例時,要用補間動畫(tween animation)來替代直接的Drawable對象的傳遞。
示例XML
以下XML片段顯示了如何把一個繪制資源添加到XML布局中的一個ImageView元素中:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="#55ff0000"
android:src="@drawable/my_image"/>
關於使用項目資源的更多信息,請看資源和資產文檔(http://developer.android.com/guide/topics/resources/index.html)
從資源XML中創建一個Drawable對象
現在,你應該已經熟悉了Android的用戶界面的開發原則。因此也就理解了定義XML中對象所帶來的強大功能和靈活性。這種方法從View對象到Drawable對象都適用。如果創建了一個Drawable對象,它初始並不依賴應用程序代碼或用戶界面中定義的一個變量,而是把它定義在一個XML中,這是一個好的選擇。即使是在用戶與應用程序交互期間要改變Drawable對象的屬性,也應該考慮把該對象定義在XML中,這樣一旦它被實例化,就可以隨時來修改它的屬性。
一旦用XML來定義Drawable對象,就要把該定義保存在工程的res/drawable/目錄中,然后通過調用Resources.getDrawable()方法來獲取和實例化該對象。
任何提供了inflate()方法的Drawable子類都能夠被定義在XML中,並能夠被應用程序實例化。每個提供XML填充能力的Drawable對象,都采用了特殊的XML屬性來幫助定義對象的屬性。有關每個Drawable子類如何在XML中定義的信息,請看對應類文檔。
示例
以下XML定義了一個TransitionDrawable對象:
<transitionxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:drawable="@drawable/image_expand">
<itemandroid:drawable="@drawable/image_collapse">
</transition>
這個XML要保存在res/drawable/expand_collapse.xml文件中,以下代碼會示例化該TransitionDrawable對象,並把它作為ImageView對象的內容來設置:
Resources res = mContext.getResources();
TransitionDrawable transition =(TransitionDrawable)
res.getDrawable(R.drawable.expand_collapse);
ImageView image =(ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);
然后,這個過渡效果能夠使用以下代碼來運行:
transition.startTransition(1000);
Android畫布和圖形繪制---Canvas and Drawables(四)
本文譯自:http://developer.android.com/guide/topics/graphics/2d-graphics.html
形狀繪制
在想要動態的繪制一些二維圖形的時候,ShapeDrawable對象將會滿足你的需要。用ShapeDrawable對象能夠編程繪制任何能夠想象得到的原始形狀和主題樣式。
ShapeDrawable類是Drawable類的一個子類,因此能夠在任何期望使用Drawable對象的地方使用ShapeDrawable對象---如用setBackgroundDrawable()方法設置View對象的背景。當然,也可以用繪制的形狀作為自己定制的View對象,然后把它添加到你的布局中。因為ShapeDrawable類有自己的draw()方法,所以能夠在View.onDraw()方法執行期間創建一個繪制ShapeDrawable圖形的View子類。以下代碼只是這種處理一個基本的擴展,它用ShapeDrawable對象來繪制一個View視窗:
publicclassCustomDrawableViewextendsView{
privateShapeDrawable mDrawable;
publicCustomDrawableView(Context context){
super(context);
int x =10;
int y =10;
int width =300;
int height =50;
mDrawable =newShapeDrawable(newOvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protectedvoid onDraw(Canvas canvas){
mDrawable.draw(canvas);
}
}
在上例的構造器中,ShapeDrawable是作為一個OvalShape對象來定義的,然后給它設定了一個顏色和邊框。如果不設置邊框,那么形狀就不會被繪制;如果沒有設置顏色,那么默認的顏色是黑色。
用這個定制的View對象,能夠繪制任何想要的形狀。在上面的例子中,我們在一個Activity中用編程的方式繪制了一個形狀:
CustomDrawableView mCustomDrawableView;
protectedvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mCustomDrawableView =newCustomDrawableView(this);
setContentView(mCustomDrawableView);
}
如果想要從XML布局中,而不是在Activity中來繪制這個定制的圖形,那么CustomDrawable類必須重寫View(Context, AttributeSet)構造器,該構造器會在從XML中填充View對象時被調用。然后把這個CustomDrawable元素添加到XML中,如:
<com.example.shapedrawable.CustomDrawableView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
ShapeDrawable類(像在android.graphics.drawable包中的其他一些Drawable類型)允許用公共的方法定義各種屬性。其中有些屬性可能需要調整,包括:透明度、顏色過濾、抖動、不透明和顏色等。
也能夠使用XML定義初始的繪制形狀。更多的信息,請閱讀繪圖資源(Drawable Resources)文檔中的形狀繪制(Shape Drawables)一節( http://developer.android.com/guide/topics/resources/drawable-resource.html#ShapeAndroid畫布和圖形繪制---Canvas and Drawables(五)
Nine-patch
NinePatchDrawable圖形是可拉伸的位圖圖片,Android系統會根據View對象中的內容來自動的調整背景圖片。使用NinePatch圖片的一個例子就是標准Android按鈕的背景圖片---按鈕必須根據字符串的長度來拉伸背景圖片。NinePathc圖形繪制的是一個標准的PNG圖片,它包含了一個像素寬的邊框。圖片文件的擴展名必須是.9.png,並且要保存到工程的res/drawable/目錄中。
邊框被用於定義圖片的拉伸和靜態區域。通過在邊框的左邊和上邊(其他邊框的像素應該完成透明或是白色的)繪制一個或多個1像素寬的黑線來指定一個可拉伸的區域。可有多個可拉伸的區域,但它們的相對尺寸都相同,最大的區域始終要保持最大的區域。
還可以通過繪制右邊線和底邊線來定義一個圖片的可選繪制區域(有效的填充線)。如果一個View對象設置NinePatch圖片作為它的背景,並且給該View對象指定了文本,那么它就會自我拉伸,以便所有的文本都能夠被填充在由右邊線和底邊線(如果包括的話)所設計的內部區域,如果不包括填充線,Android系統會使用左邊線和上邊線來定義該繪制區域。
要澄清不同邊線間的差異,為了拉伸圖片,左邊線和上邊線定義的圖片的像素被允許復制。底邊線和右邊線定義了圖片內相對區域,View對象的內容被允許放到這個區域內。
下圖是用於定義按鈕的一個NinePatch圖片文件:
這個NinePatch圖片用左邊線和上邊線定義了一個可拉伸的區域,用右邊線和底邊線定義了一個可繪圖的區域。為了拉伸圖片,在上面的那個圖片中,灰色的點划線定指定了圖片將要被重復的區域。在下面的那個圖片中,粉色的矩形指明了View的內容被允許放置的區域。如果該區域不同完全填充View對象的內容,那么該圖片就會被拉伸,直到內容被完全填充。
Draw 9-patch工具,使用WYSIWYG圖形編輯器,提供非常方便的創建NinePatch圖片的方法。如果定義的可拉伸區域在繪制構件的過程中存在像素復制的風險,它甚至會產生一個警告。
示例XML
該布局XML演示了如何把一個NinePatch圖片添加到一對按鈕中(NinePatch圖片被保存在res/drawable/my_button_background.9.png中):
<Buttonid="@+id/tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:text="Tiny"
android:textSize="8sp"
android:background="@drawable/my_button_background"/>
<Buttonid="@+id/big"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerInParent="true"
android:text="Biiiiiiig text!"
android:textSize="30sp"
android:background="@drawable/my_button_background"/>
要注意的是,寬度和高度屬性都被設置成了”wrap_content”,以便按鈕能夠根據文本尺寸來調整大小。
以下是使用上面顯示的圖片和XML定義所展現的兩個按鈕。注意,按鈕的寬度和高度是如何根據文本的尺寸來拉伸背景圖片的。