11.1A: Creating a simple Canvas object

Contents:

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

  1. Draw your graphics or animations on a View object in your layout. By using this option, the system's rendering pipeline handles your graphics—it's your responsibility to define the graphics inside the view.
  2. Draw your graphics in a Canvas object. To use this option, you pass your Canvas to the appropriate class' onDraw(Canvas) method. You can also use the drawing methods in Canvas. This option also puts you in control of any animation.

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.

When you want to draw shapes or text into a view on Android, you need:

  • A Canvas object. Very simplified, a Canvas is a logical 2D drawing surface that provides methods for drawing onto a bitmap.
  • An instance of the Bitmap class which represents the physical drawing surface and gets pushed to the display by the GPU.
  • A View instance associated with the bitmap.
  • A Paint object that holds the style and color information about how to draw geometries, text, and on bitmap.
  • The Canvas class also provides methods for clipping views. Clipping is the action of defining geometrically what portion of the canvas the user sees in the view. This visible portion is called the viewport in graphics terminology.

The figure below shows all the pieces required to draw to a canvas.  In order to draw into a <code>View,</code> the <code>View</code> needs a <code>Bitmap</code>, a <code>Canvas</code>, and <code>Paint</code>.

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:

  • Create apps with Android Studio and run them on a physical or virtual mobile device.
  • Add a click event handler to a View.
  • Create and display a custom View.

What you will LEARN

You will learn how to:

  • Create a Canvas object, associate it with a Bitmap object, and display the bitmap in an ImageView.
  • Style drawing properties with a Paint object.
  • Draw on a canvas in response to a click event.

What you will DO

  • Create an app that draws on the screen in response to touch events.

App overview

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.  Screenshot for the final drawing of the SimpleCanvas app

Task 1. Create a canvas and draw on it

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.

Note: The benefit of doing an example without a custom view is that you can focus on drawing on the canvas. For a real-world application, you are likely to need a custom view.

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>
    
  4. Add the following color resources to the colors.xml file.
     <color name="colorRectangle">#455A64</color>
     <color name="colorBackground">#FFFFD600</color>
    
  5. 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;
    
  2. 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();
    
  3. 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);
    
  4. Create a Bitmap member variable mBitmap.

    The Bitmap represents the pixels that are shown on the display.

    private Bitmap mBitmap;
    
  5. 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;
    
  6. Create two Rect variables, mRect and mBounds and initialize them to rectangles.
     private Rect mRect = new Rect();
     private Rect mBounds = new Rect();
    
  7. 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;
    
  8. Create a MULTIPLIER constant initialized to 100. You will need this constant later, for generating random colors.
     private static final int MULTIPLIER = 100;
    
  9. 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);
     }
    
  2. 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);
    
  3. In onCreate(), set the color of mPaint to mColorBackground.
     mPaint.setColor(mColorBackground);
    
  4. 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);
    
  5. Get a reference to the image view.
     mImageView = (ImageView) findViewById(R.id.myimageview);
    
    Important: You cannot create the Canvas in onCreate(), because the views have not been laid out, so their final size is not available. When you create a custom view in a later lesson, you learn different ways to initialize your drawing surface.

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) {}
    
  2. 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;
    
  3. Add an if-else statement for (mOffset == OFFSET).

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

  4. mOffset == OFFSET. The app is only in this state the first time the user taps. Create the Bitmap, associate it with the View, create the Canvas, fill the background, and draw some text. Increase the offset.

  5. mOffset != OFFSET and the offset is smaller than half the screen width and height. Draw a rectangle with a computed color and increase the offset.
  6. mOffset != OFFSET and the offset is equal to or larger than half the screen width and height. Draw a circle with the text "Done!".
    if (mOffset == OFFSET) {
    } else {
      if (mOffset < halfWidth && mOffset < halfHeight) {
      } else {
      }
    }
    
  7. Inside the outer if statement (mOffset == OFFSET), create a Bitmap.

  8. Supply the width and height for the bitmap, which are going to be the same as the width and height of the view.

  9. Pass in a Bitmap.config configuration object. A bitmap configuration describes how pixels are stored. How pixels are stored affects the quality (color depth) as well as the ability to display transparent/translucent colors. The ARGB_8888 format supports Alpha, Red, Green, and Blue channels for each pixel. Each color is encoded in 8 bits, for a total of 4 bytes per pixel.
    mBitmap = Bitmap.createBitmap(vWidth, vHeight, Bitmap.Config.ARGB_8888);
    
  10. Associate the bitmap with the ImageView.
     mImageView.setImageBitmap(mBitmap);
    
  11. Create a Canvas and associate it with mBitmap, so that drawing on the canvas draws on the bitmap.
     mCanvas = new Canvas(mBitmap);
    
  12. Fill the entire canvas with the background color.
     mCanvas.drawColor(mColorBackground);
    
  13. 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);
    
  14. Increase the offset.
     mOffset += OFFSET;
    
  15. 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();
    

    Note: If you run the app at this point, it should start with a blank screen, and when you tap, the screen fills and the text appears.

  16. In the else block, inside the if statement

  17. Set the color of mPaint. This code generates the next color by subtracting the current offset times a multiplier from the original color. A color is represented by a single number, so you can manipulate it in this way for some fun effects.

  18. Change the size of the mRect rectangle to the width of the view, minus the current offset.
  19. Draw the rectangle with mPaint styling.
  20. Increase the offset.

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:

  2. Set the color of mPaint.

  3. Draw a circle with the paint.
  4. Get the "Done" string and calculate its bounding box, then calculate x and y to draw the text at the center of the circle. The bounding box defines a rectangle that encloses the string. You cannot make calculations on a string, but you can use the dimensions of the bounding box to calculate, its center.
    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);
    }
    
  5. Run your app and tap multiple times to draw. Rotate the screen to reset the app.

Solution code

Android Studio project: SimpleCanvas.

Summary

  • To draw on the display of a mobile device with Android you need a View, a Canvas, a Paint , and a Bitmap object.
  • The Bitmap is the physical drawing surface. The Canvas provides an API to draw on the bitmap, the Paint is for styling what you draw, and the View displays the Bitmap.
  • You create a Bitmap, associate it with a View, create a Canvas with a Paint object for the Bitmap, and then you can draw.
  • You must invalidate() the view when your are done drawing, so that the Android System redraws the display.
  • All drawing happens on the UI thread, so performance matters.

The related concept documentation is in The Canvas class.

Learn more

Android developer documentation:

results matching ""

    No results matching ""