2012年5月1日 星期二

[AndroidGraphic] Canvas and Drawables

Drawing 2D graphics:
  • 由 layout 來畫。適用於不是太複雜、不需動態更動、像遊戲需要要求效率的圖 。
  • 直接用 Canvas 來畫出你的圖。適用於會規律性更新內容的圖。
    • You personally call the appropriate class's onDraw() method (passing it your Canvas), or one of the Canvas draw...() methods (like drawPicture()).
    • Doing in which thread:
      • In UI thread: call invalidate() and then handle the onDraw() callback.
      • In a separate thread: wherein you manage a SurfaceView and perform draws to the Canvas as fast as your thread is capable (you do not need to request invalidate()).

Draw with a Canvas
  • 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. 
  • 有兩種使用方式
    • On a View (extends View)
      • Using within the onDraw() callback method, the Canvas is provided for you and you need only place your drawing calls upon it. 
    • On a SurfaceView
      • You can also acquire a Canvas from SurfaceHolder.lockCanvas(), when dealing with a SurfaceView object.
  • 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:
// 在此 Canvas 上所畫的內容,會以傳入的 Bitmap 為底。
Bitmap b = Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);

// 然後可以藉由 Canvas.drawBitmap() 得到所畫的 Bitmap。
// Drawable has its own draw() method that takes your Canvas as an argument.
drawable.draw(canvas); // 將 drawable 畫到 canvas 上。
It's recommended that you ultimately draw your final graphics through a Canvas offered to you by View.onDraw() or SurfaceHolder.lockCanvas().


On a View
如果 view 中沒有含大量處理過程或是會一直更新畫面(frame-rate speed, ex: chess game, a snake game, or another slowly-animated application),那麼可以繼承 View 客制自己的 view,在 View.onDraw() 中利用 Android Framework 傳入的 Canvas 畫圖。
  • 當要畫出你的 View 時,Android Framework 會呼叫 View.onDraw() ,但 The Android framework 只會在需要時去呼叫 onDraw(),不過你也可以藉 invalidate() 去觸發此呼叫,你的 view 會被重繪。
  • invalidate() 只能在 UI/main thread 中被呼叫,若是要在 non-UI thread 中呼叫,改使用 postInvalidate().


On a SurfaceView (android.view.SurfaceView)
  • The SurfaceView is a special subclass of View that offers a dedicated drawing surface within the View hierarchy.
  • 可以控制它的格式和大小。
  • SurfaceView 會負責把它的 surface 放在螢幕上正確的位置。
Purpose:
  • 在 View hierarchy 還沒準備好前,便可以在 secondary thread 中利用 SurfaceView 和它的 Canvas 繪畫且在 secondary thread rendering 它到 screen。
    • To offer this drawing surface to an application's secondary thread, so that the application isn't required to wait until the system's View hierarchy is ready to draw.
    • To provide a surface in which a secondary thread can render into the screen. 
  • Instead, a secondary thread that has reference to a SurfaceView can draw to its own Canvas at its own pace.
Process:
  • 藉 SurfaceHolder interface 的 getHolder() 可以取得和它相關的 surface。
  • 當 SurfaceView's window 為可視時,Surface 會被建立。
  • Implement surfaceCreated(SurfaceHolder) and surfaceDestroyed(SurfaceHolder) 就可以看到當 window 顯示與隱藏時,Surface 也被建立與摧毀。
To be aware of some threading semantics:
  • All SurfaceView and SurfaceHolder.Callback methods will be called from the thread running the SurfaceView's window (typically the main thread of the application).
    • They thus need to correctly synchronize with any state that is also touched by the drawing thread.
  • You must ensure that the drawing thread only touches the underlying Surface while it is valid -- between SurfaceHolder.Callback.surfaceCreated() and SurfaceHolder.Callback.surfaceDestroyed().

How to use?
  • Create a new class that extends SurfaceView.
  • The class should also implement SurfaceHolder.Callback.
  • This subclass is an interface that will notify you with information about the underlying Surface, such as when it is created, changed, or destroyed.
  • Inside your SurfaceView class is also a good place to define your secondary Thread class, which will perform all the drawing procedures to your Canvas.

Note:
  • Instead of handling the Surface object directly, you should handle it via a SurfaceHolder.
  • When your SurfaceView is initialized, get the SurfaceHolder by calling getHolder().
  • Notify the SurfaceHolder that you'd like to receive SurfaceHolder callbacks (from SurfaceHolder.Callback) by calling addCallback() (pass it this).
  • Override each of the SurfaceHolder.Callback methods inside your SurfaceView class.
  • On each pass you retrieve the Canvas from the SurfaceHolder, the previous state of the Canvas will be retained. In order to properly animate your graphics, you must re-paint the entire surface. 
    • For example, you can clear the previous state of the Canvas by filling in a color with drawColor() or setting a background image with drawBitmap(). Otherwise, you will see traces of the drawings you previously performed.


Drawables
  • A Drawable is a general abstraction for "something that can be drawn."
  • In application, 一個 drawable 只會擁有一份,狀態是共享,因此不管怎麼取得擁有,修改了其中的狀態,則原圖(其他 instance) 也會改變。
    • Each unique resource in your project can maintain only one state, no matter how many different objects you may instantiate for it. 
    • For example, if you instantiate two Drawable objects from the same image resource, then change a property (such as the alpha) for one of the Drawables, then it will also affect the other. So when dealing with multiple instances of an image resource, instead of directly transforming the Drawable, you should perform a tween animation.
  • 可以使用 mutate() 取得原圖,這樣這個就不會被改變到。

There are three ways to define and instantiate a Drawable:
  • Using an image saved in your project resources;
    • Supported file types are PNG (preferred), JPG (acceptable) and GIF (discouraged).
    • 放在 res/drawable/ 下的圖片,在 build 的過程,aapt 會自動以失真最少的方式壓縮圖片簡省空間。
    • 若是圖片不想被壓縮則可以放在 res/raw/ 下。
  • Using an XML file that defines the Drawable properties;
    • This philosophy caries over from Views to Drawables. 
    • If there is a Drawable object that you'd like to create, which is not initially dependent on variables defined by your application code or user interaction, then defining the Drawable in XML is a good option. 
    • Even if you expect your Drawable to change its properties during the user's experience with your application, you should consider defining the object in XML, as you can always modify properties once it is instantiated.
    • Any Drawable subclass that supports the inflate() method can be defined in XML and instantiated by your application.
  • Using the normal class constructors.


Shape Drawable
  • When you want to dynamically draw some two-dimensional graphics, a ShapeDrawable object will probably suit your needs.
  • With a ShapeDrawable, you can programmatically draw primitive shapes and style them in any way imaginable.


* Reference
- Canvas and Drawables | Android Developers

沒有留言:

張貼留言