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 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.
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(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).
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).
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 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 Method | Purpose | Key Paint Parameters |
|---|---|---|
| drawCircle | Draw a circle | centerX, centerY, radius, paint |
| drawRect | Draw a rectangle | left, top, right, bottom, paint |
| drawLine | Draw a line | startX, startY, endX, endY, paint |
| drawPath | Draw an arbitrary path | path, paint |
| drawBitmap | Draw a bitmap image | bitmap, srcRect, dstRect, paint |
| drawText | Draw text | text, x, y, paint |
| drawArc | Draw an arc or sector | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Draw an oval | ovalRect, paint |
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.
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 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.
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.
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.
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() 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.
Let’s look at practical examples of overriding onDraw to create custom Views in Android using Kotlin.
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.
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)
}
}
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.
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)
}
}
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.
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
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.
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.
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.
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.
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
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.
Read also