Applications such as video games should be drawing to the Canvas on its own. However, there's more than one way to do this:
- In the same thread as your UI Activity, wherein you create a custom View component in your layout, call
and then handle theinvalidate()
callback.onDraw()
- Or, 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 requestinvalidate()
).
However, if you need to create a new Canvas, then you must define theBitmap
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);It's recommended that you ultimately draw your final graphics through a Canvas offered to you by
View.onDraw()
or
SurfaceHolder.lockCanvas()
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
. This indicates that you'd like your View to be drawn and Android will then call your invalidate()
onDraw()
method (though is not guaranteed that the callback will be instantaneous).
Note: In order to request an invalidate from a thread other than your main Activity's thread, you must callpostInvalidate()
> Instead of handling the Surface object directly, you should handle it via a SurfaceHolder
. So, when your SurfaceView is initialized, get the SurfaceHolder by calling
. You should then notify the SurfaceHolder that you'd like to receive SurfaceHolder callbacks (from getHolder()
SurfaceHolder.Callback
) by callingaddCallback()
(pass it this). Then override each of the SurfaceHolder.Callback
methods inside your SurfaceView class.
> A Drawable
is a general abstraction for "something that can be drawn." You'll discover that the Drawable class extends to define a variety of specific kinds of drawable graphics, including BitmapDrawable
, ShapeDrawable
,PictureDrawable
, LayerDrawable
, and several more. Of course, you can also extend these to define your own custom Drawable objects that behave in unique ways.
There are three ways to define and instantiate a Drawable: using an image saved in your project resources; using an XML file that defines the Drawable properties; or using the normal class constructors. Below, we'll discuss each the first two techniques (using constructors is nothing new for an experienced developer).
Note: Image resources placed in res/drawable/
may be automatically optimized with lossless image compression by the aapt
tool during the build process. For example, a true-color PNG that does not require more than 256 colors may be converted to an 8-bit PNG with a color palette. This will result in an image of equal quality but which requires less memory. So be aware that the image binaries placed in this directory can change during the build. If you plan on reading an image as a bit stream in order to convert it to a bitmap, put your images in the res/raw/
folder instead, where they will not be optimized.
> The following code snippet demonstrates how to build an ImageView
that uses an image from drawable resources and add it to the layout.
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); }
> Here's some XML that defines a TransitionDrawable:
<transition xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/image_expand"> <item android:drawable="@drawable/image_collapse"> </transition>
With this XML saved in the file res/drawable/expand_collapse.xml
, the following code will instantiate the TransitionDrawable and set it as the content of an 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);
> Here's a basic extension of the View class that does just this, to draw a ShapeDrawable as a View:
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); } }
> A NinePatch drawable is a standard PNG image that includes an extra 1-pixel-wide border. It must be saved with the extension .9.png
, and saved into the res/drawable/
directory of your project.
To clarify the difference between the different lines, the left and top lines define which pixels of the image are allowed to be replicated in order to stretch the image. The bottom and right lines define the relative area within the image that the contents of the View are allowed to lie within.
This NinePatch defines one stretchable area with the left and top lines and the drawable area with the bottom and right lines. In the top image, the dotted grey lines identify the regions of the image that will be replicated in order to stretch the image. The pink rectangle in the bottom image identifies the region in which the contents of the View are allowed.