Drawing on the canvas of a View

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.

How to do it...

Drawing to a canvas can be easily done by creating a new view that derives from View, and then overriding the OnDraw() method:

  1. First, we will need our custom 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) {
      }
    }
  2. Next, we override the 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);
    }
  3. Drawing on a canvas requires a Paint object, and as this is expensive to create, we create it only once in a field:
    private Paint paint = new Paint();
  4. To avoid objects appearing too large or too small on different devices, we make use of the 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:

  1. As we will be reusing the Paint object each time we draw, we can reset any properties using the Reset() method:
    paint.Reset();
  2. As drawing round shapes may result in jagged edges, we can enable anti-aliasing:
    paint.AntiAlias = true;
  3. To start off drawing, we could paint or clear the entire view using a single color:
    canvas.DrawColor(Color.CornflowerBlue);
  4. If we want to draw a shape, such as a solid green circle, we set the properties on the Paint object, and then use the DrawCircle() method:
    paint.SetStyle(Paint.Style.Fill);
    paint.Color = Color.DarkGreen;
    canvas.DrawCircle(centerX, centerY, radius*density, paint);
  5. We can draw a border on the circle at the setting by the paint style to 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);
  6. We can also draw text, in this case, centered and at the top, on the screen:
    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);
  7. If we are making a game, we can request that the view be updated again as soon as possible using the Invalidate() method:
    Invalidate();

How it works...

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.

Note

Android will only redraw a View instance when it determines that it has changed. To force a redraw as soon as possible, the Invalidate() method can be invoked.

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.

Note

Most, if not all, drawing operations onto a Canvas require a Paint instance.

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.

Tip

Because devices have different screen densities, drawing operations should account for higher density screens to avoid things appearing too small.

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.

Tip

The origin of a rectangle is at the top-left corner, that of a circle is at the center, and for text is, it is at the baseline. The baseline is below the text, but above the descent.

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.

There's more...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset