11.1: The Canvas class

Contents:

Drawing in Android

In Android, you have several techniques available for implementing custom 2D graphics and animations. Which techniques you choose depends on the type of graphics you want to display, as well as the needs of your app.

  • Drawables

    A Drawable is a graphic that can be drawn to the screen. You create Drawable objects for things such as textured buttons or frame-by-frame animations and display them into a standard or custom view. The drawing of your graphics is handled by the system's view hierarchy drawing process. Using a drawable is your best choice when you want to draw simple graphics that do not need to change dynamically and are not part of a performance-intensive game. For more informations, see Drawables and the chapter about drawables in Android Developer Fundamentals.

  • Canvas

You can do your own custom 2D drawing using the drawing methods of the Canvas class. Draw to a Canvas when your app needs to regularly re-draw itself. In this chapter, you learn how to create and draw on a canvas.

  • SurfaceView

An instance of SurfaceView is a drawing surface that is part of your view hierarchy and managed and rendered by the system along with the view hierarchy. One of the purposes of this class is to provide a surface in which a secondary thread can render into the screen. The challenge is that you have to correctly manage your thread to only draw to the surface when it exists. The SurfaceView chapter includes an example of using a SurfaceView.

In addition, the following techniques are available but not covered in this course:

  • Hardware Acceleration. Beginning in Android 3.0, you can hardware-accelerate most of the drawing done by the Canvas APIs to further increase their performance.
  • OpenGL. Android supports OpenGL ES 1.0 and 2.0, with Android framework APIs as well as natively with the Native Development Kit (NDK). Using the framework APIs is desireable when you want to add a few graphical enhancements to your application that are not supported with the Canvas APIs, or if you desire platform independence and don't demand high performance.

What is a Canvas object?

When you want to draw shapes or text into a view on Android, you can do so via a Canvas object. Very simplified:

  • A Canvas is a 2D drawing surface that provides methods for drawing to a bitmap.
  • The bitmap, an instance of the Bitmap class is associated with a View instance that displays it.
  • A Paint object holds the style and color information about how to draw geometries (such as line, rectangle, oval, and paths), text, and bitmap.

Canvas defines shapes that you can draw on the screen, while Paint defines the color, style, font, and so forth, of each shape you draw.

The Canvas class also provides methods for clipping. Clipping is the action of defining geometrically what portion of the canvas is shown to the user in the view.

The figure below shows all the pieces required to draw.  In order to draw into a view, the view needs a bitmap, a canvas, and paint

The types of operations you can perform on a canvas include:

  • Fill the whole canvas with color.
  • Draw shapes, such as rectangles, arcs, paths styled as defined in a Paint object.
  • Draw text styled by the properties in a Paint object.
  • Save the current Canvas state and restore a previous state.
  • Apply transformations, such as translation, scaling, or custom transformations.
  • Clip, that is, apply a shape or path to the Canvas that defines its visible portions.

Steps for creating and drawing on a Canvas object

After you have a project and activity, you need the following to work with a Canvas object.

  1. Create a custom View class. You can extend any view that has the features you need. You do not need a custom View to draw, as discussed in the Simple Canvas practical. Typically you use a custom View so that you can draw by overriding the onDraw() method of the View.

    public class MyCanvasView extends View {...}

  2. In onCreate() of the MainActivity, set the content View of the activity to be an instance of your custom View.

    myCanvasView = new MyCanvasView(this); setContentView(myCanvasView);

  3. In the constructor of the custom View, create a Paint object and set initial Paint properties. Initialize member variables and get a reference to the context. You cannot create your Canvas here because the View has not been inflated yet and thus has no size.

     MyCanvasView(Context context) {
        this(context, null);
     }
    
     public MyCanvasView(Context context, AttributeSet attributeSet) {
        super(context);
    
        int backgroundColor;
    
        mDrawColor = ResourcesCompat.getColor(getResources(),
                        R.color.opaque_orange, null);
        backgroundColor = ResourcesCompat.getColor(getResources(),
                R.color.opaque_yellow, null);
    
        // Holds the path we are currently drawing.
        mPath = new Path();
        // Set up the paint with which to draw.
        mPaint = new Paint();
        mPaint.setColor(backgroundColor);
        // Smoothes out edges of what is drawn without affecting shape.
        mPaint.setAntiAlias(true);
        // Dithering affects how colors with higher-precision than the device
        // are down-sampled.
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.STROKE); // default: FILL
        mPaint.setStrokeJoin(Paint.Join.ROUND); // default: MITER
        mPaint.setStrokeCap(Paint.Cap.ROUND); // default: BUTT
        mPaint.setStrokeWidth(12); // default: Hairline-width (really thin)
     }
    
  4. In the custom View, override onSizeChanged() and create a Bitmap, then create a Canvas with the Bitmap. The onSizeChanged() method is called when your View is first assigned a size, and again if the size of your View changes for any reason. Calculate positions, dimensions, and any other values related to your View's size in onSizeChanged().
     @Override
     protected void onSizeChanged(int width, int height,
                                 int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);
        // Create bitmap, create canvas with bitmap, fill canvas with color.
        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        mCanvas.drawColor(mDrawColor);
     }
    
  5. In the custom View, override the onDraw() method. What happens in the onDraw() method can be as short as drawing a Path that was calculated in another method, or it can include drawing, transformations, and clipping, which are discussed below.
     @Override
     protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // First, draw the bitmap as created.
        canvas.drawBitmap(mBitmap, 0, 0, mPaint);
        // Then draw the path on top, styled by mPaint.
        canvas.drawPath(mPath, mPaint);
     }
    
  6. If any drawing is to happen in response to user motion, override onTouchEvent(). In this example, when the user drags their finger, and then lifts it off the screen, the x,y coordinates are handed off for some drawing action. You must call invalidate() every time the screen needs to be redrawn.

     @Override
     public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
    
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchStart(x, y);
                // No need to invalidate because we are not drawing anything.
                break;
            case MotionEvent.ACTION_MOVE:
                touchMove(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                touchUp();
                invalidate();
                break;
            default:
                // do nothing
        }
        return true;
     }
    
    Note:This work all happens on the UI thread, which is why you should not use this technique for lengthy operations. For more complex operations, manage a SurfaceView and perform draws to the Canvas as fast as your thread is capable. See the SurfaceView chapter.

Drawing shapes and text

You can draw primitive shapes using predefined methods, and use a Path to create any shape you need.

See the documentation for details of each method, and the practicals for examples on how these methods are used.

Transformations

Transformations are operations that change the canvas itself:

  • Use translate() to move the origin of the canvas. For example, when you are drawing the same shape in multiple places, instead of recalculating the shape, you can move the origin of the canvas and draw the same shape again. See the Applying clipping to a canvas practical for an example.
    canvas.translate(dx, dy);
    
  • Use rotate to turn the Canvas by a number of degrees.
    canvas.rotate(180);
    
  • Skew the Canvas by calling the skew() method. You can achieve interesting effects with text using skewing.
    mPaint.setTextSize(120);
    canvas.translate(100, 1800);
    canvas.skew(0.2f, 0.3f);
    canvas.drawText("Clipping", 400, 60, mPaint);
    
    See the Canvas documentation for details on these and additional methods.

Clipping

Clipping is a method for defining regions of an image, canvas, or bitmap that are selectively drawn or not drawn onto the screen. One purpose of clipping is to reduce overdraw . When you reduce overdraw, you minimize the number of times a pixel or region of the display is drawn, in order to maximize drawing performance. You can also use clipping to create interesting effects in user interface design and animation.

The following screenshot from the Applying clipping to a canvas practical shows how you can clip and combine clipping regions.  Screenshot for the ClippingExample app.

Canvas provides methods for clipping, and you can also clip by defining a custom path.

  • Use canvas.clipRect() to set a rectangular clipping region.
  • Define a Path and apply it with canvas.clipPath() for a custom clipping region. For example, to create a circular clipping region.

    • CCW stands for counterclockwise and indicates in which direction the circle should be drawn.
    • Region.Op.DIFFERENCE specifies how the clipping region should be applied to the canvas. See the Region.Op documentation for other operators.

Here is the code:

mPath.addCircle(mCircleRadius, mClipRectBottom-mCircleRadius, mCircleRadius, Path.Direction.CCW);
canvas.clipPath(mPath, Region.Op.DIFFERENCE);

For a list of all clipping methods and supported operators for different versions of Android, see the Canvas documentation.

quickReject()

The quickReject() method allows you to check whether a specified rectangle or path would lie completely outside the currently visible regions, after all transformations have been applied.

The quickReject() method is incredibly useful when you are constructing more complex drawings and need to do so as fast as possible. With quickReject(), you can decide efficiently which objects you do not have to draw at all, and there is no need to write your own intersection logic.

  • The method returns true if the rectangle or path would not be visible at all. For partial overlaps, you still have to do your own checking.
  • The EdgeType is either AA (Antialiased: Treat edges by rounding-out, because they may be antialiased) or BW (Black-White: Treat edges by just rounding to nearest pixel boundary) for just rounding to the nearest pixel.
    boolean quickReject(float left, float top, float right, float bottom,Canvas.EdgeType type)
    boolean quickReject(RectFrect,Canvas.EdgeType type)
    boolean quickReject(Pathpath,Canvas.EdgeType type)

Saving and restoring a canvas

The activity context maintains a stack of drawing states. Each state includes the currently applied transformations and clipping regions. You can't remove clipping regions. Undoing a transformation by reversing it is error-prone, as well as chaining too many transformations relative to each other. Translation is straightforward to reverse, but if you also stretch, rotate, or custom deform, it gets complex quickly. Instead, save the state of the canvas, apply your transformations, draw, and then restore the previous state.

canvas.save();
mPaint.setTextSize(120);
canvas.translate(100, 1800);
canvas.skew(0.2f, 0.3f);
canvas.drawText("Skewing", 400, 60, mPaint);
canvas.restore();

canvas.save();
mPaint.setColor(Color.CYAN);
canvas.translate(600, 1800);
canvas.drawText("Save/Restore", 400, 60, mPaint);
canvas.restore();

For a complete example, see the Clipping Canvas practical.

The related exercises and practical documentation is in Advanced Android: Practicals.

Learn more

Android developer docs:

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

results matching ""

    No results matching ""