onDraw() — What It Is, Call Mechanism and Method Override

Author: IT Sectr Published: 2026-07-22 Reading time: 10 min

onDraw() is a key method of the View class in the Android SDK responsible for drawing the content of a view. The system calls onDraw on every update of the View’s appearance — during initial rendering, after invalidate(), or when dimensions change. According to Android Developers Documentation (2026), overriding onDraw using the Canvas API is the primary way to create custom graphics in Android applications.

Key Takeaways

  • onDraw(Canvas) — a View method called by the system to draw content on the Canvas
  • invalidate() — a request to redraw the View, leading to onDraw being called in the next frame cycle
  • Canvas API provides methods drawCircle, drawBitmap, drawText, drawPath and others for drawing
  • The Android system performs drawing on the UI thread, so onDraw should be lightweight and fast
  • Hardware acceleration automatically speeds up drawing through the GPU, starting from Android 3.0 (API 11)

What is onDraw()?

onDraw(Canvas canvas) is a protected method of the android.view.View class, which is called by the Android system when a View needs to be drawn. A developer overrides this method to implement custom drawing: shapes, text, images, gradients, animations. The Canvas parameter is a graphical surface on which all drawing operations are performed.

The method signature is simple: override fun onDraw(canvas: Canvas). Inside this method, you should not create new objects (to avoid GC overhead), perform long operations, or work with the network. View also includes the onDraw method in its lifecycle between onLayout (position calculation) and dispatchDraw (drawing child elements).

When inheriting from View, overriding onDraw is mandatory to display any content — otherwise the View will be empty. When inheriting from existing subclasses (TextView, ImageView, Button), overriding onDraw is optional and is used to add additional graphics on top of the standard content.

View Drawing Cycle

Android performs View drawing in a strictly defined sequence called from the main thread (UI thread). The full cycle consists of three phases: measure (dimension calculation), layout (position calculation), and draw (rendering). onDraw is part of the draw phase and is called after the View’s dimensions and position have already been determined.

Draw phase: dispatchDraw and onDraw

draw(Canvas canvas) is a public method of View that calls onDraw and dispatchDraw. The draw method is not meant to be overridden — instead, onDraw is overridden. After onDraw executes for the current View, dispatchDraw is called, which recursively invokes draw for all child Views (if the current View is a ViewGroup container).

When the system calls onDraw

onDraw is called under the following conditions: 1) the View becomes visible for the first time (after setVisibility(VISIBLE)); 2) the app calls invalidate() on this View or its parent; 3) the View’s size changes (new layout); 4) an animation associated with the View changes (via ValueAnimator or ObjectAnimator); 5) the View’s state changes (pressed, focused, enabled).

Hardware acceleration

Since Android 3.0 (API 11), most Canvas operations are hardware-accelerated through the GPU. A developer can check whether acceleration is enabled via canvas.isHardwareAccelerated(). With hardware acceleration, some Canvas operations are not supported (e.g., clipPath with a non-rectangular region) and may be ignored or cause errors.

Canvas API for Custom Graphics

Canvas provides a rich set of drawing methods. All methods work in the current coordinate system, which can be modified through transformations (translate, rotate, scale, skew). Each method accepts a Paint object that defines color, style, line thickness, effects, and anti-aliasing.

Canvas MethodPurposeKey Paint Parameters
drawCircleDraw a circlecenterX, centerY, radius, paint
drawRectDraw a rectangleleft, top, right, bottom, paint
drawLineDraw a linestartX, startY, endX, endY, paint
drawPathDraw an arbitrary pathpath, paint
drawBitmapDraw a bitmap imagebitmap, srcRect, dstRect, paint
drawTextDraw texttext, x, y, paint
drawArcDraw an arc or sectorovalRect, startAngle, sweepAngle, useCenter, paint
drawOvalDraw an ovalovalRect, paint

The Paint object

Paint defines the drawing style: color (setColor), line thickness (setStrokeWidth), fill style (setStyle — FILL, STROKE, FILL_AND_STROKE), anti-aliasing (setAntiAlias), effects (setShadowLayer, setMaskFilter), and gradients (setShader). It is recommended to create and configure Paint once in the View’s constructor rather than inside onDraw to avoid object creation in the drawing loop.

Coordinates and transformations

Canvas uses a coordinate system where the X-axis points right and the Y-axis points down. The origin (0, 0) is the top-left corner of the View. Before calling draw methods, a developer can change the coordinate system via canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). All subsequent drawing operations will be performed in the transformed system.

onDraw Performance

onDraw executes on the main UI thread, and its duration directly affects the frame rate (fps). To maintain 60 fps, the onDraw method must complete within 16 milliseconds. Any slowdown leads to frame drops (jank), which visually appears as animation stuttering or scrolling lag.

Performance onDraw rules

Key rules for onDraw optimization include: 1) do not create objects inside onDraw — all Paint, Path, Rect objects should be class fields; 2) do not allocate memory (new) — this triggers garbage collection (GC) and frame drops; 3) cache complex operations in a Bitmap via Bitmap.createBitmap and draw the prepared Bitmap; 4) do not perform I/O operations or resource loading.

Using clipRect for optimization

clipRect limits the drawing area to a specified rectangle. It is used to prevent the Canvas from wasting time drawing pixels outside the visible area. It is especially effective when scrolling large lists or zooming: set clipRect to the visible area (dirty rect), and the Canvas will only draw what is visible.

Layer — save and restore

canvas.save() saves the current Canvas state (transformation and clip) onto a stack. canvas.restore() restores the last saved state. This is necessary for temporary transformations: save the state, apply translate/rotate, draw, then restore. save and restore are lightweight and do not affect performance when used reasonably (fewer than 10–15 nesting levels).

invalidate and postInvalidate

invalidate() is a View method that tells the system that the View’s content is outdated and needs to be redrawn. Calling invalidate initiates a new draw cycle in which onDraw will be called. invalidate must only be called from the UI thread (main thread). For calling from a background thread, use postInvalidate.

postInvalidate() is a thread-safe version of invalidate that sends a redraw request to the main thread’s queue via Handler. It is used in background tasks when the View needs to be updated after data loading or calculations complete. It can be combined with runOnUiThread for UI synchronization.

For partial redrawing, use invalidate(Rect dirty) or invalidate(int l, int t, int r, int b). This tells the system that only the specified area has changed, not the entire View. Android optimizes drawing by redrawing only the dirty rect, significantly improving performance for animations with partial updates.

Kotlin Code Examples

Let’s look at practical examples of overriding onDraw to create custom Views in Android using Kotlin.

Custom View — Circular Progress

This example creates a circular progress indicator with animated filling. onDraw draws two circles: a gray background circle and a colored fill circle whose arc is determined by the current progress. Calling invalidate in setProgress ensures redrawing when the value changes.

kotlin
class CircularProgressView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private val bgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.parseColor("#E0E0E0")
        isAntiAlias = true
    }

    private val progressPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.BLUE
        isAntiAlias = true
        strokeCap = Paint.Cap.ROUND
    }

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 1f)
            invalidate()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val size = minOf(width, height) - 24f
        val oval = RectF(12f, 12f,
                          12f + size, 12f + size)

        canvas.drawArc(oval, 270f, 360f, false, bgPaint)
        canvas.drawArc(oval, 270f,
                        progress * 360f,
                        false, progressPaint)
    }
}

Custom View — Gradient Background with Text

This example draws a rectangle with a gradient and text on top of it. The gradient is created via LinearGradient (Shader), and the text via drawText. Both Paint objects are created once in the constructor, while onDraw only updates the rectangle size based on the current View width.

kotlin
class GradientTextView(context: Context)
    : View(context) {

    private val shaderPaint = Paint().apply {
        isAntiAlias = true
    }

    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 48f
        isAntiAlias = true
        textAlign = Paint.Align.CENTER
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val gradient = LinearGradient(
            0f, 0f, width.toFloat(), 0f,
            Color.parseColor("#FF6B35"),
            Color.parseColor("#FF0040"),
            Shader.TileMode.CLAMP
        )
        shaderPaint.shader = gradient
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), shaderPaint)

        val xCenter = width / 2f
        val yCenter = height / 2f
        val yOffset = (textPaint.descent() +
                        textPaint.ascent()) / 2f

        canvas.drawText("Hello from Canvas",
                      xCenter, yCenter - yOffset,
                      textPaint)
    }
}

Animated Clock Using invalidate

This example creates an animated clock that updates the drawing every 1000 milliseconds. postDelayed inside onDraw calls invalidate after one second, creating an infinite redraw loop. Clock hands are drawn via drawLine with different lengths and thicknesses.

kotlin
class AnalogClockView(context: Context)
    : View(context) {

    private val handPaint = Paint().apply {
        style = Paint.Style.STROKE
        isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val cx = width / 2f
        val cy = height / 2f
        val radius = minOf(cx, cy) - 20f

        val now = Calendar.getInstance()
        val hours = now.get(Calendar.HOUR) % 12
        val minutes = now.get(Calendar.MINUTE)
        val seconds = now.get(Calendar.SECOND)

        val secondAngle = Math.toRadians(
            seconds * 6f - 90f)
        handPaint.strokeWidth = 2f
        handPaint.color = Color.RED
        canvas.drawLine(cx, cy,
            cx + cos(secondAngle) * radius * 0.8f,
            cy + sin(secondAngle) * radius * 0.8f,
            handPaint)

        postDelayed({ invalidate() }, 1000)
    }
}

Frequently Asked Questions

Can onDraw be called directly?

No, you should not call onDraw directly. To request a redraw, use invalidate() (from the UI thread) or postInvalidate() (from a background thread). The system will decide when to call onDraw and will optimize drawing by merging multiple invalidate calls into a single pass.

Why is onDraw not called after invalidate?

Reasons: the View is hidden (setVisibility(GONE) or INVISIBLE); the View has not been added to the Window hierarchy; the View’s size is 0 (width or height = 0); setWillNotDraw(true) is called (true by default for ViewGroup). Check each of these points when debugging.

How often can onDraw be called?

At 60 fps, onDraw can be called up to 60 times per second. At 120 fps (on devices with 120 Hz displays) — up to 120 times. Each call should complete within approximately 8–16 milliseconds. During active animations via ObjectAnimator or ValueAnimator, the frequency is determined by the number of animation frames.

What is the difference between onDraw and dispatchDraw?

onDraw is responsible for drawing the View’s own content. dispatchDraw is responsible for drawing the child Views (in ViewGroup). dispatchDraw is called after onDraw and recursively traverses all child elements. For a regular View (not a ViewGroup), dispatchDraw is empty.

Can Canvas be used outside onDraw?

Yes, Canvas can be created for a Bitmap via Canvas(bitmap) and drawn on in any thread. This is used for pre-rendering complex graphics in the background. However, the Canvas passed to onDraw belongs to the system and can only be used on the UI thread inside the onDraw method.

Summary

  • onDraw(Canvas) — a key View method for custom drawing using the Canvas API in Android
  • Drawing cycle consists of measure, layout, and draw phases — onDraw is called during the draw phase
  • Canvas API includes drawCircle, drawRect, drawPath, drawBitmap, drawText, and other methods
  • invalidate() requests redrawing from the UI thread, postInvalidate() from a background thread
  • Partial redrawing via invalidate(Rect) optimizes rendering by updating only the dirty region
  • onDraw performance is critical: creating objects inside the method triggers GC and frame drops
  • Hardware acceleration (since API 11) automatically accelerates Canvas operations through the GPU

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also