Sometimes using the animation and shape APIs are not enough to create the image or app that we want to create. This may be to create a game or draw a complex image.
Drawing to a canvas can be easily done by creating a new view that derives from View
, and then overriding the OnDraw()
method:
View
instance that implements the constructors that allow us to instantiate the view from either code or a layout file:public class CustomView : View { public CustomView(Context context) : base(context) { } public CustomView(Context context, IAttributeSet attrs) : base(context, attrs) { } public CustomView( Context context, IAttributeSet attrs, int defStyle) : base (context, attrs, defStyle) { } }
OnDraw()
method. This method draws the actual frame and will be called each time the view needs refreshing:protected override void OnDraw(Canvas canvas) { base.OnDraw(canvas); }
Paint
object, and as this is expensive to create, we create it only once in a field:private Paint paint = new Paint();
Density
property of the DisplayMetrics
object:float density = Resources.DisplayMetrics.Density;
Now, we can start the actual drawing process. We specify a series of operations on the Canvas
object, which is passed into the OnDraw()
method:
Paint
object each time we draw, we can reset any properties using the Reset()
method:paint.Reset();
paint.AntiAlias = true;
canvas.DrawColor(Color.CornflowerBlue);
Paint
object, and then use the DrawCircle()
method:paint.SetStyle(Paint.Style.Fill); paint.Color = Color.DarkGreen; canvas.DrawCircle(centerX, centerY, radius*density, paint);
Stroke
and then redrawing the circle:paint.SetStyle(Paint.Style.Stroke); paint.StrokeWidth = 2*density; paint.Color = Color.Black; canvas.DrawCircle(centerX, centerY, radius, paint);
paint.TextAlign = Paint.Align.Center; paint.SetStyle(Paint.Style.Fill); paint.TextSize = 20*density; Rect measureRect = new Rect(); paint.GetTextBounds(text, 0, text.Length, measureRect); var textHeight = measureRect.Height(); canvas.DrawText(text, Width / 2f, textHeight, paint);
Invalidate()
method:Invalidate();
Sometimes using the animation and shape APIs are not enough to create the image or app that we want to create. This may be required to create a game or draw a complex image. To draw a custom image, we make use of a View
instance and its Canvas
attribute. This is especially useful for games or apps that need to update a portion of the screen, or the entire screen, very frequently.
This is easy to do as all it requires is that we inherit from View
and override the OnDraw()
method. This method is provided a Canvas
instance onto which we draw our graphics. Each time that Android determines that the view needs to be redrawn, this method will be called.
We can also request that the view be redrawn by invoking the Invalidate()
method on the View
instance. This will cause the entire view to be refreshed and redrawn. If we only want a partial area to be redrawn, we can pass the rectangle bounds to an overload. The Invalidate()
method must be called from the UI thread; all other threads must use the PostInvalidate()
methods.
If we were going to make a game, we could update the game state and then draw the game out onto the canvas provided by the OnDraw()
method. Then, we can request a new update of the view by invoking Invalidate
from the OnDraw()
method. This will create the update-draw loop required in some games.
A Paint
instance holds the information of how to draw the particular request, and it ultimately controls what the resulting image will look like. The paint object also provides a few extra properties that allow us to enhance the drawing, such as antialiasing using through the AntiAlias
property.
Creating an instance of Paint
is quite expensive and should probably be done once and reused for all drawing operations. To prevent previous property values of Paint
being accidentally used, we can reset the entire object to its initial state using the Reset()
method.
When drawing on a canvas, we need to keep in mind that different devices have different screen densities. This is essential if we want images to appear with similar sizes on all devices. On devices with high densities, everything will appear much smaller than expected, and possibly too small for the user to work with. We can obtain the screen density using the Density
property on the DisplayMetrics
instance returned by the view's Resources
property. We can use this property to multiply our calculations by in order to get similar sized images, even on devices with very high densities.
Once we have our Paint
instance and the Canvas
, we can start drawing our image. Every time the OnDraw()
method is called, we are provided with a blank, transparent canvas to draw on. To prevent other controls or views from showing through, we can clear the canvas using the DrawColor()
method. This method accepts a color, which will then be used to fill the entire canvas.
The only thing left to do is to draw our image onto the canvas. This takes place by setting the paint properties and then drawing something. For example, to draw a solid green circle, we first set the paint to fill using the SetStyle()
method with the Paint.Style.Fill
value. Then, we set the color using the Color
property and use the Color.DarkGreen
value. As Color
is a type, we are not limited to the predefined colors, but can construct any color we wish.
Now, we can draw the actual circle using the DrawCircle()
method of the canvas. All the draw methods take parameters of the image we want to draw and then the paint instance, in this case, the center's X and Y coordinates, the radius of the circle, and finally the Paint
instance. We need to remember to multiply the radius by the screen density to avoid the circle being tiny on high density devices.
To draw a circle outline, instead of a filled circle, we can use the SetStyle()
method on Paint
and pass in Paint.Style.Stroke
. Then, we call the same DrawCircle()
method passing the same parameters. As the paint object has changed properties, the result will be different. If we want to draw a circle with a border, we first draw the solid circle, then draw the circle stroke.
There are many methods for drawing geometric shapes, such as DrawRect()
and DrawPath()
, as well as methods for drawing whole images such as DrawBitmap()
or DrawPicture()
. We can also write text using the DrawText()
method. Drawing text is very much the same as drawing anything, but we should remember to first set the font size using the TextSize
property. Again, we need to remember to multiply the screen density. There is also the TextAlign
property that enables us to align text to the left, right, or center of drawing a rectangle.
Often, we would like to position text more precisely, such as below or above an object. As text is not positioned from the usual top-left corner, but rather from the baseline, we have to ensure that we take into account the fact that most of the text will be above the specified coordinates.
We can measure the size of the text bounding box using the GetTextBounds()
method of Paint
. Measuring a string, or portion of a string, allows us to position the text and take into consideration the different origin. The text measurements are taken using the styles and properties set on the Paint
object.
We can make use of the ability to draw on a Canvas
instance when making an app or game that provides augmented reality. We can overlay the SurfaceView
instance used by the camera with a transparent View
instance. Then, we process the camera image stream and draw onto the View
instance, providing the illusion that we are drawing onto the camera stream, or rather revealing something that was otherwise hidden in the real world.