安卓提供了一組繪製二維圖形的 API(參考官方文檔:Canvas and Drawables | Android Developers),這組 API 容許開發者經過將自定義圖形繪製到畫布上或修改現有視圖來實現視圖的定製,繪製二維圖形,一般有如下兩種方式:html
當繪製的圖形既不須要動態變化,也不是一個高性能遊戲的一部分時(好比,當你想在一個靜態應用中顯示一個靜態視圖或預約義的動畫時,你應該將圖形引入到視圖中)第一種視圖繪製的方式是最好的選擇。當程序中的視圖須要按期重繪自身時(好比:一些視頻遊戲就須要不斷的去重繪自身以達到動畫交互的效果),第二種將是最優的選擇。實現第二種繪製的方式有兩種:android
當你編寫的程序須要進行圖形繪製或對圖形的動畫進行控制時,你須要經過畫布來完成這些操做。畫布適合做爲將圖形繪製到實際表面上的接口,由於它擁有全部的 draw 方法。經過畫布繪製,其實是將圖形繪製在了屏幕的位圖上。在回調方法 onDraw() 中進行繪製時,畫布會由 onDraw() 方法提供,咱們只須要將繪圖放置到畫布上便可。當在處理 SurfaceView 對象時,咱們能夠從 SurfaceHolder.lockCanvas() 方法獲取一個畫布。(這兩個場景將在如下部分中討論)然而,若是你須要建立一個新的畫布,就必須定義一個能夠用來展現圖形繪製的位圖。Canvas 的繪製須要位圖支持,你能夠按照如下方式創建一個畫布:
canvas
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b);
畫布建立好後,就能夠利用畫布在以前定義的位圖上進行圖形繪製了。畫布完成繪製後,可使用 Canvas.drawBitmap(Bitmap,...) 方法中的任何一個將位圖繪製到其餘畫布上。但推薦的作法是儘可能使用由 View.onDraw() 或 SurfaceHolder.lockCanvas() 方法提供的畫布來進行最終圖形的繪製。(見下節)編輯器
Canvas 類爲咱們提供了一套繪圖方法,如 drawBitmap(...), drawRect(...), drawText(...) 等。你可能使用的其餘類也有 draw() 方法。好比,你可能有一些須要繪製到 Canvas 上的 Drawable 對象。Drawable 有一個能夠將 Canvas 做爲參數的 draw() 方法。
ide
若是你的應用不須要處理大量的幀運算(多是一個象棋遊戲、貪食蛇、或者是其餘包含緩慢動畫的應用),這時你就須要建立一個自定義控件並在該控件的 View.onDraw() 方法中使用 Canvas 進行圖形繪製。Android Framework 層爲此提供了一個能夠執行各類繪製方法的預處理畫布。函數
實現自定義控件,須要繼承 View 類(或其子類)並重寫 onDraw() 方法。該方法將在視圖進行繪製的時候由 Android framework 層調用。你能夠經過 onDraw() 方法中提供的 Canvas 調用全部的繪製方法。佈局
Android Framework 層只在必要的時候調用 onDraw()方法。每當你的應用準備要進行繪製時,你必須經過調用 invalidate() 方法來確保這個 View 是無效的。執行這個方法代表你想讓視圖進行重繪,而後 Android 就會調用 onDraw()方法進行視圖重繪(但並不保證這個回調會立刻執行)。post
在自定義視圖組件的 onDraw() 方法中,可使用給定的 Canvas 執行各類 Canvas.draw...() 方法,或者使用其餘類的 onDraw() 方法(在其餘類中將 Canvas 做爲參數傳入)進行繪製。一旦 onDraw() 方法執行結束,Android Framework 層將會把由 Canvas 繪製的位圖交給系統來處理。性能
▐ 注:若是是從一個線程中(非 UI 線程)去請求視圖失效,必須調用 postInvalidate()。
優化
有關擴展View類的信息,請參考(參考官方文檔:Building Custom Components | Android Developers)。
有關示例應用程序,請參閱貪吃蛇遊戲,在 SDK 示例文件夾:<you're-sdk-directory>/samples/Snake/(源碼:Snake)。
SurfaceView 是 View 的一個特殊子類,它在視圖層提供了一個專用的繪製平面。其目的是能夠將該平面提供給應用的輔助線程,從而使應用無需等待系統視圖層準備完畢就能夠直接繪製。相反,一個引用了 SurfaceView 的輔助線程能夠以自身的速度在 Canvas 上進行繪製。
首先,你須要建立一個類繼承 SurfaceView。同時,這個類還應該實現 SurfaceHolder.Callback。這個子類是一個監控 Surface 什麼時候被建立、修改、銷燬事件的接口。這些事件十分重要,經過這些事件咱們能知道什麼時候能夠開始繪製,是否須要根據新的表面特性進行調整,以及什麼時候中止繪製並強制結束一些任務。在 SurfaceView 的繼承類裏還能夠定義執行繪製動做的輔助線程類。
你應該經過 SurfaceHolder 來處理 Surface 對象,而不是直接處理它。因此,當你的 SurfaceView 進行初始化時,須要經過調用 getHolder() 方法來獲取 SurfaceHolder。而後你應該調用 addCallback() 方法來通知 SurfaceHolder 你想接收 SurfaceHolder 的回調方法(來自:SurfaceHolder.Callback)。最後再重寫 SurfaceView 繼承類裏的每個 SurfaceHolder.Callback 方法。
若是要從子線程中將圖形繪製到畫布表面,就必須將 SurfaceHandler 傳遞到子線程,並經過 lockCanvas() 方法獲取 Canvas。如今,你就能夠在 SurfaceHolder 提供給你的 Canvas 上繪製圖形了。若是用 Canvas 完成了繪製,須要調用 unlockCanvasAndPost() 並將 Canvas 對象傳遞給該方法。Surface 將會以你傳遞的 Canvas 對象進行繪製。每次程序須要重繪的時候都須要對 Canvas 順序執行鎖定和解鎖操做。
▐ 注:每次從 SurfaceHolder 得到的 Canvas 都會保留先前的狀態,爲了確保圖形繪製正確,你必須重繪整個 surface。例如,你可使用 drawColor() 填充顏色或使用 drawBitmap() 設置一個背景圖片來清除 Canvas 的先前狀態。不然,你會看到先前執行繪製時的痕跡。
有關示例應用程序,請參閱貪 Lunar Lander 遊戲,在 SDK 示例文件夾:<your-sdk-directory>/samples/LunarLander/(源碼:LunarLander)。或參考 Sample Code。
Android 爲圖形和圖像的繪製提供了一個定製的 2D 圖形庫。在 android.graphics.drawable 包下能夠找到用於繪製 2 緯圖形的經常使用類。
本文討論了使用 Drawable 對象來繪製圖形,以及如何使用 Drawable 子類的基本知識。有關使用 Drawables 完成幀動畫的信息,請參考 Drawable Animation。
Drawable 是 「一些能夠被繪製事物」 的抽象。你會發如今 Drawable 類基礎上又擴展了各類特定的圖形繪製子類,包括 BitmapDrawable,ShapeDrawable,PictureDrawable,LayerDrawable 等。同時,你也能夠經過繼承這些子類來自定義 Drawable 對象。
有三種方式來定義和實例化一個 Drawable:使用一張保存在項目資源文件夾中的圖片;使用一個定義了 Drawable 屬性的 XML 文件;或使用普通類的構造函數。下面,咱們將對前 2 種技術進行討論(對於有經驗的開發者來講使用構造函數並不陌生)。
在應用中添加圖形的簡單方法是從項目資源文件中引用一個圖片文件。支持的文件類型有 PNG(首選),JPG(可接受)和 GIF(不推薦)。應用的 icon、logo 或遊戲中的圖片資源應該優先選用這種技術。
使用圖像資源,須要將文件添加到項目的 res/drawable/ 目錄下。代碼或 XML 文件均可以引用該目錄下的圖像資源。不管那種方式的引用,都只會引用文件的資源 ID,而不是包含擴展名的文件(例如:my_image.png 的引用 ID 爲 my_image)。
▐ 注:在 build 過程當中,AAPT 會自動對 res/drawable/ 文件夾下的圖片資源進行無損壓縮。例如:一個不超過 256 色的真彩 PNG 圖片或許會被調色板轉換爲一個 8 位的 PNG 圖片。這樣的壓縮會使圖片具備相同的顯示效果,但卻佔用更少的內存資源。因此須要明確的是在 res/drawable/ 目錄下的圖像二進制文件會在 build 過程當中發生改變。若是你想以流的形式讀取圖片,並將流轉換爲一個位圖,能夠將圖片放置到 res/raw/ 目錄下,在這個目錄下的圖片不會被優化。
下面的代碼片斷演示瞭如何從 drawable 資源中獲取一張圖片來繪製一個 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 對象來處理。要想這樣作,能夠像下面的代碼同樣從 resource 中獲取一個 Drawable 對象:
Resources res = mContext.getResources();
Drawable myImage = res.getDrawable(R.drawable.my_image);
▐ 注:項目中的每一個資源,不論你爲它實例化多少不一樣的對象,這個資源都只維護一個狀態。例如:你使用相同的圖片資源實例化了 2 個 Drawable 對象,而後改變其中一個 Drawable 對象的屬性(如 Alpha),那麼另外一個 Drawable 也會受到影響。因此在處理同一個圖片資源的多個實例時,應該執行一個補間動畫,而不是直接去改變 Drawable。
下面的代碼片斷演示瞭如何將一個資源對象添加到 XML 佈局的 ImageView 控件上。
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/my_image" android:tint="#55ff0000" />
更多關於使用項目資源的信息,請參考 Resources and Assets。
如今,你應該對開發用戶界面的原則比較熟悉了。所以,你也應該明白在 XML 文件中定義視圖對象的做用和靈活性。這種理念從 Views 延續到 Drawables。若是你想建立一個 Drawable 對象,而且這個對象不依賴於應用程序代碼定義的變量或用戶的交互,那麼在 XML 文件中定義 Drawable 是最好的選擇。即便你但願 Drawable 在用戶與應用的交互過程當中改變自身的屬性,你也應該考慮在 XML 中定義 Drawable 對象,由於你能夠在它實例化後隨時修改其屬性。
定義了 Drawable 的 XMl 文件須要保存在項目的 res/drawable/ 目錄下。而後,調用 Resources.getDrawable() 來獲取和實例化對象,這個過程須要傳遞 XML 文件的資源 ID(參見下面的例子)。
全部支持 inflate() 方法的 Drawable 子類均可以定義在 XML 文件中並能夠被實例化。支持 XML 屬性擴展的 Drawable 能夠經過 XML 屬性來定義對象的屬性(詳情請參考類的引用)。如何在 XML 文件中定義對象屬性可參閱每一個 Drawable 子類的說明文檔。
下面是定義了 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>
將此 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);
而後設置過渡時間爲 1 秒:
transition.startTransition(1000);
更多關於每一個 Drawable 支持的 XML 屬性請參考上面列出的 Drawable 類。
ShapeDrawable 對象能夠知足動態繪製二維圖形的需求,用 ShapeDrawable,開發者能夠經過代碼的方式來繪製圖形和定義風格。
ShapeDrawable 對 Drawable 進行了擴展,所以你能夠對一個須要 Drawable 的地方使用該對象(或許是一個視圖的背景,能夠調用 setBackgroundDrawable())。固然,你也能夠把它當成要添加到佈局文件上的自定義圖形來繪製。由於 ShapeDrawable 有本身的 draw() 方法,因此你能夠建立一個 View 的子類,並在該子類的 View.onDraw() 方法中使用 ShapeDrawable 進行圖形繪製。下面是對 View 類的基本擴展,在這個擴展中展現瞭如何使用 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); } }
在構造函數中,設置 ShapeDrawable 的形狀爲 OvalShape(橢圓形),而後再爲該圖形設置顏色和邊界。若是不設置邊界,圖形將不會被繪製,若是不設置顏色,將會默認爲黑色。
開發者能夠以本身喜歡的方式來繪製自定義視圖,咱們能夠用上面的代碼在 Activity 中繪製圖形:
CustomDrawableView mCustomDrawableView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCustomDrawableView = new CustomDrawableView(this); setContentView(mCustomDrawableView); }
若是你想在 XML 佈局中繪製自定義視圖,就必須重寫自定義視圖類的 View(Context, AttributeSet) 構造方法,由於這個方法會在 XML 實例化視圖的時候被調用。而後就能夠在 XML 中添加一個自定義視圖的元素進去了,代碼以下:
<com.example.shapedrawable.CustomDrawableView android:layout_width="fill_parent" android:layout_height="wrap_content" />
ShapeDrawable 類容許經過公用方法來定義 drawable 的各類屬性,好比:Alpha透明度(alpha transparency),濾色鏡(color filter),抖動(dither),不透明度(opacity)和顏色(color)。
你還能夠經過 XML 來定義基本圖形。更多信息,請參考 Drawable Resources。
NinePatchDrawable 圖形是一種可拉伸的位圖圖像,若是你將這種圖形做爲控件背景,Android 將自動調整圖形大小以適應視圖內容。一個使用 NinePatch 的例子是 Android 標準按鈕的背景,這個背景能夠自適應不一樣長度的字符串。一個 NinePatch drawable 是一個包含1像素邊框的標準 PNG 圖片,它的擴展名必須爲 .9.png,而且它應該存放到項目的 res/drawable/ 目錄下。
邊界用於定義圖像的伸縮性和靜態區域。因此能經過1像素或更寬的黑線在邊框的左邊和頂部來設置可拉伸的部分(另外一邊界應該是透明或白色的),你能夠根據你的須要來設置可拉伸部分:它們的相對大小保持不變,因此最大的部分依然保持最大。
還能夠在圖片的右側和底部畫線(其實是填充線)來指定可拉伸部分。若是一個 View 對象以 NinePatch 做爲自身的背景而且設置了文本,NinePatch 將在底部線和右側線的交匯區域進行拉伸以適應文字的顯示。若是沒有填充線,Android 將使用左側和頂部線來定義這個繪製區域。
不一樣的線有着不一樣的做用,圖片左側和頂部的線定義了圖片的哪些像素能夠被複制用以拉伸圖片。圖片底部和右側的線定義了視圖內容放置的區域。
下面是一個用於按鈕的 NinePatch 示例文件:
NinePatch 的左側和頂部的線定義了一個可拉伸區域,底部和右側的線定義了一個繪製區域。第一張圖片中灰色虛線標識了圖片拉伸的時候哪些像素會被複制。第二章圖片的粉紅色矩形標識了視圖的內容將被繪製在什麼地方。若是這個繪製區域和視圖內容尺寸不匹配,那麼圖像將被拉伸以適應視圖內容。
Draw 9-patch 是一個所見即所得圖形編輯器,它提供了一個很是方便的方式來建立 NinePatch 圖片。它甚至還對拉伸區域像素複製結果的風險提出警告。
下面是一些演示如何爲按鈕添加 NinePatch 圖片的 XML 示例佈局(該 NinePatch 圖像保存爲 res/drawable/my_button_background.9.png)。
<Button id="@+id/tiny" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerInParent="true" android:background="@drawable/my_button_background" android:text="Tiny" android:textSize="8sp" /> <Button id="@+id/big" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:background="@drawable/my_button_background" android:text="Biiiiiiig text!" android:textSize="30sp" />
注意,寬高都設置爲「wrap_content」,就可使該按鈕整齊地適應文本內容。