Advanced Android Canvas Mastery
Canvas Drawing Cycle
The Drawing Cycle
When you create a custom view in Android, you get a blank canvas to draw whatever you want. The Android framework controls this process. You don't call the drawing methods yourself. Instead, the system calls a specific method on your view when it's time to render: onDraw(Canvas canvas).
Think of the Canvas object as your drawing surface, and the onDraw() method as your designated time to draw on it. Your job is to override this method and provide the drawing instructions. The system handles the when and how, ensuring your view appears on screen at the right moment.
Canvas API in Android is a drawing framework which helps us to draw custom design like line, circle or even a rectangle.
Before drawing can happen, your view needs to know its size. The system initiates a measure and layout pass. First, onMeasure() is called to determine the view's dimensions. Then, onLayout() is called to assign a size and position to the view within its parent. Only after the view has a defined space on the screen will onDraw() be executed.
Triggering a Redraw
Your view won't automatically redraw itself just because some state has changed. You must tell the system that your view's appearance is outdated and needs a refresh. There are two primary ways to do this: invalidate() and requestLayout().
Use invalidate() when only the visual content of your view has changed. For example, if you change a color, update a line's position, or alter text. This call is efficient. It tells the system that the view's size and position are still valid, and only its drawing commands need to be re-executed. The system will then schedule a call to your view's onDraw() method.
Use requestLayout() when the bounds of your view might have changed. This is a more extensive operation. It signals that the view’s size or shape is different, which could affect the entire view hierarchy. This call triggers a full measure and layout pass for the whole tree, starting from the top. The system will call onMeasure(), then onLayout(), and finally onDraw(). Because it's more resource-intensive, you should only call requestLayout() when necessary.
| Method | When to Use | Resulting Calls |
|---|---|---|
invalidate() | Content changes (color, text, path) | onDraw() |
requestLayout() | Size or shape changes | onMeasure(), onLayout(), onDraw() |
Performance is Key
The onDraw() method is at the heart of smooth animations and responsive user interfaces. To avoid stuttering or lag, it must execute quickly. A common mistake is allocating new objects inside onDraw().
Creating objects like a Paint or Path involves memory allocation. When this happens repeatedly in a draw loop, it forces the garbage collector to work more often, which can pause your application's main thread and cause dropped frames. The key is to initialize these objects once and reuse them.
// The wrong way: allocating objects in onDraw()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint myPaint = new Paint(); // Bad! Don't allocate here.
myPaint.setColor(Color.RED);
canvas.drawCircle(100, 100, 50, myPaint);
}
// The right way: initialize once, reuse in onDraw()
private Paint circlePaint;
public MyCustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// Initialize objects here in the constructor.
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Reuse the object.
canvas.drawCircle(100, 100, 50, circlePaint);
}
A view's constructor is the perfect place for this one-time setup. For objects that depend on the view's size, you can initialize or update them in onSizeChanged(), a method called whenever the view's dimensions change.
Understanding the Canvas
The Canvas uses a simple 2D coordinate system. The origin, (0,0), is at the top-left corner of the view's bounds. The x-axis increases to the right, and the y-axis increases downwards. This is standard for most screen-based graphics systems.
When you call a drawing command like canvas.drawCircle(cx, cy, radius, paint), the cx and cy coordinates are relative to your view's top-left corner, not the entire screen. This makes your drawing logic self-contained and portable.
By understanding this drawing cycle and its performance implications, you can create custom views that are both powerful and efficient. Always separate your initialization logic from your rendering loop to ensure a smooth experience for your users.
What is the primary method you must override in a custom Android view to perform drawing operations?
You've created a custom view that draws a shape. You have a public method that changes the shape's color. After updating the color variable, which method should you call to see the change on screen?