This codelab is part of the Advanced Android Development training course, developed by the Google Developers Training team. You will get the most value out of this course if you work through the codelabs in sequence.

For complete details about the course, see the Advanced Android Development overview.

Introduction

When you want to create your own custom 2D drawings for Android, you can do so in the following ways.

Drawing to a view is a good choice when you want to draw simple graphics that don't need to change dynamically, and when your graphics aren't part of a performance-intensive app such as a game. For example, you should draw your graphics into a view when you want to display a static graphic or predefined animation, within an otherwise static app. For more information, read Drawables.

Drawing to a canvas is better when your app needs to regularly redraw itself. Apps, such as video games, should draw to the canvas on their own. This practical shows you how to create a canvas, associate it with a bitmap, and associate the bitmap with an ImageView for display.

To draw shapes or text into a view on Android, you need:

The figure below shows all the pieces required to draw to a canvas.

You do not need a custom view to draw, as you learn in this practical. Typically you draw by overriding the onDraw() method of a View, as shown in the next practicals.

See the Graphics Architecture series of articles for an in-depth explanation of how the Android framework draws to the screen.

What you should already know

You should be able to:

What you'll learn

What you'll do

As you build the SimpleCanvas app, you learn how to create a canvas, associate it with a bitmap, and associate the bitmap with an ImageView for display.

When the user clicks in the app, a rectangle appears. As the user continues to click, the app draws increasingly smaller rectangles onto the canvas.

When you start the app, you see a white surface, the default background for the ImageView.

Tap the screen, and it fills with orange color, and the underlined text "Keep tapping" is drawn. For the next four taps, four differently colored inset rectangles are drawn. On the final tap, a circle with centered text tells you that you are "Done!", as shown in the screenshot below.

If the device is rotated, the drawing is reset, because the app does not save state. In this case, this behavior is "by design," to give you a quick way of clearing the canvas.

You can associate a Canvas with an ImageView and draw on it in response to user actions. This basic implementation of drawing does not require a custom View. You create an app with a layout that includes an ImageView that has a click handler. You implement the click handler in MainActivity to draw on and display the Canvas.

1.1 Create the SimpleCanvas project and layout

  1. Create the SimpleCanvas project with the Empty Activity template.
  2. In activity_main.xml, replace the TextView with an ImageView that fills the parent.
  3. Add an onClick property to the ImageView and create a stub for the click handler called drawSomething(). Your XML code should look similar to this.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.simplecanvas.MainActivity">

   <ImageView
       android:id="@+id/myimageview"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:onClick="drawSomething"/>

</android.support.constraint.ConstraintLayout>
  1. Add the following color resources to the colors.xml file.
<color name="colorRectangle">#455A64</color>
<color name="colorBackground">#FFFFD600</color>
  1. Add the following string resources to the strings.xml file.
<string name="keep_tapping">Keep tapping.</string>
<string name="done">Done!</string>

1.2 Create the SimpleCanvas member variables and constants

In MainActivity.java:

  1. Create a Canvas member variable mCanvas.

    The Canvas object stores information on what to draw onto its associated bitmap. For example, lines, circles, text, and custom paths.
private Canvas mCanvas;
  1. Create a Paint member variable mPaint and initialize it with default values.

    The Paint objects store how to draw. For example, what color, style, line thickness, or text size. Paint offers a rich set of coloring, drawing, and styling options. You customize them below.
private Paint mPaint = new Paint();
  1. Create a Paint object for underlined text. Paint offers a full complement of typographical styling methods. You can supply these styling flags when you initialize the object or set them later.
private Paint mPaintText = new Paint(Paint.UNDERLINE_TEXT_FLAG);
  1. Create a Bitmap member variable mBitmap.

    The Bitmap represents the pixels that are shown on the display.
private Bitmap mBitmap;
  1. Create a member variable for the ImageView, mImageView.

    A view, in this example an ImageView, is the container for the bitmap. Layout on the screen and all user interaction is through the view.
private ImageView mImageView;
  1. Create two Rect variables, mRect and mBounds and initialize them to rectangles.
private Rect mRect = new Rect();
private Rect mBounds = new Rect();
  1. Create a constant OFFSET initialized to 120, and initialize a member variable mOffset with the constant. This offset is the distance of a rectangle you draw from the edge of the canvas.
private static final int OFFSET = 120;
private int mOffset = OFFSET;
  1. Create a MULTIPLIER constant initialized to 100. You will need this constant later, for generating random colors.
private static final int MULTIPLIER = 100;
  1. Add the following private member variables for colors.
private int mColorBackground;
private int mColorRectangle;
private int mColorAccent;

1.3 Fix the onCreate method and customize the mPaint member variable

In MainActivity.java:

  1. Verify that onCreate() looks like the code below.
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
}
  1. In onCreate(), get color resources and assign them to the color member variables.
mColorBackground = ResourcesCompat.getColor(getResources(),
        R.color.colorBackground, null);
mColorRectangle = ResourcesCompat.getColor(getResources(),
        R.color.colorRectangle, null);
mColorAccent = ResourcesCompat.getColor(getResources(),
        R.color.colorAccent, null);
  1. In onCreate(), set the color of mPaint to mColorBackground.
mPaint.setColor(mColorBackground);
  1. In onCreate(), set the color for mPaintText to the theme color colorPrimaryDark, and set the text size to 70. Depending on the screen size of your device, you may need to adjust the text size.
mPaintText.setColor(
    ResourcesCompat.getColor(getResources(),
        R.color.colorPrimaryDark, null)
    );
mPaintText.setTextSize(70);
  1. Get a reference to the image view.
mImageView = (ImageView) findViewById(R.id.myimageview);

1.4 Implement the drawSomething() click handler method

The drawSomething() method is where all the interaction with the user and drawing on the canvas are implemented.

The drawSomething() click handler responds to user taps by drawing an increasingly smaller rectangle until it runs out of room. Then it draws a circle with the text "Done!" to demonstrate basics of drawing on canvas.

You always need to do at least the following:

  1. Create Bitmap.
  2. Associate Bitmap with View.
  3. Create Canvas with Bitmap.
  4. Draw on Canvas.
  5. Call invalidate() on the View to force redraw.

Inside the drawSomething() method, add code as follows.

  1. Create or verify the signature for the drawSomething() method.
public void drawSomething(View view) {}
  1. Get the width and height of the view and create convenience variables for half the width and height. You must do this step every time the method is called, because the size of the view can change (for example, when the device is rotated).
int vWidth = view.getWidth();
int vHeight = view.getHeight();
int halfWidth = vWidth / 2;
int halfHeight = vHeight / 2;
  1. Add an if-else statement for (mOffset == OFFSET).

When drawSomething() is called, the app is in one of three states:

if (mOffset == OFFSET) {
} else {
    if (mOffset < halfWidth && mOffset < halfHeight) {
    } else {
    }
}
  1. Inside the outer if statement (mOffset == OFFSET), create a Bitmap.
mBitmap = Bitmap.createBitmap(vWidth, vHeight, Bitmap.Config.ARGB_8888);
  1. Associate the bitmap with the ImageView.
mImageView.setImageBitmap(mBitmap);
  1. Create a Canvas and associate it with mBitmap, so that drawing on the canvas draws on the bitmap.
mCanvas = new Canvas(mBitmap);
  1. Fill the entire canvas with the background color.
mCanvas.drawColor(mColorBackground);
  1. Draw the "Keep tapping" text onto the canvas. You need to supply a string, x and y positions, and a Paint object for styling.
mCanvas.drawText(getString(R.string.keep_tapping), 100, 100, mPaintText);
  1. Increase the offset.
mOffset += OFFSET;
  1. At the end of the drawSomething() method, invalidate() the view so that the system redraws the view every time drawSomething() is executed.

When a view is invalidated, the system does not draw the view with the values it already has. Instead, the system recalculates the view with the new values that you supply. The screen refreshes 60 times a second, so the view is drawn 60 times per second. To save work and time, the system can reuse the existing view until it is told that the view has changed, the existing view is invalid, and the system thus has to recalculate an updated version of the view.

view.invalidate();
  1. In the else block, inside the if statement

Below is the complete if portion of the code.

if (mOffset < halfWidth && mOffset < halfHeight) {
      // Change the color by subtracting an integer.
      mPaint.setColor(mColorRectangle - MULTIPLIER*mOffset);
      mRect.set(
          mOffset, mOffset, vWidth - mOffset, vHeight - mOffset);
      mCanvas.drawRect(mRect, mPaint);
      // Increase the indent.
      mOffset += OFFSET;
}
  1. In the else statement, when the offset is too large to draw another rectangle:
else {
    mPaint.setColor(mColorAccent);
    mCanvas.drawCircle(halfWidth, halfHeight, halfWidth / 3, mPaint);
    String text = getString(R.string.done);
    // Get bounding box for text to calculate where to draw it.
    mPaintText.getTextBounds(text, 0, text.length(), mBounds);
    // Calculate x and y for text so it's centered.
    int x = halfWidth - mBounds.centerX();
    int y = halfHeight - mBounds.centerY();
    mCanvas.drawText(text, x, y, mPaintText);
}
  1. Run your app and tap multiple times to draw. Rotate the screen to reset the app.

Android Studio project: SimpleCanvas.

The related concept documentation is in 11.1 The Canvas class.

Android developer documentation:

To see all the codelabs in the Advanced Android Development training course, visit the Advanced Android Development codelabs landing page.