Canvas in Android — is a class from the android.graphics package that provides an API for 2D rendering on View. Canvas works as a canvas on which the developer draws geometric shapes, text, images and paths using various draw* methods. According to the Android Developers Documentation (2025), Canvas is the primary tool for custom rendering in Android and is used together with the Paint class to define styles and colors. Each onDraw() call receives a ready Canvas instance associated with the view’s bitmap buffer.
Key Takeaways
Canvas — is the central 2D graphics class in Android that provides methods for drawing on a plane. Each Canvas is attached to a Bitmap — a memory area where the results of drawing operations are recorded. When a View is redrawn, the system passes to onDraw() a Canvas already associated with that View’s buffer.
Canvas works in a coordinate system where (0,0) is the top-left corner, the X axis goes right, the Y axis goes down. All drawing operations use the current state of Canvas, including the transformation matrix, clipping area and Paint style. According to Android Documentation (2025), the Canvas API includes over 30 draw* methods for different types of graphic primitives.
Since Android 3.0 (API 11) Canvas supports hardware acceleration through DisplayList. In accelerated mode, Canvas operations are recorded into a DisplayList and then executed on the GPU. This provides a significant performance boost but imposes limitations: some Canvas operations (clipPath with non-Path clip) are not supported in accelerated mode.
Canvas provides a set of methods for drawing basic primitives. Each method accepts geometric parameters and a Paint object that defines the visual style.
| Method | Purpose | Example |
|---|---|---|
| drawRect() | Draws a rectangle | canvas.drawRect(10f, 10f, 100f, 100f, paint) |
| drawCircle() | Draws a circle | canvas.drawCircle(50f, 50f, 30f, paint) |
| drawLine() | Draws a line | canvas.drawLine(0f, 0f, 100f, 100f, paint) |
| drawText() | Draws text | canvas.drawText(“Hello”, 10f, 20f, paint) |
| drawBitmap() | Draws an image | canvas.drawBitmap(bitmap, 0f, 0f, paint) |
| drawPath() | Draws an arbitrary path | canvas.drawPath(path, paint) |
| drawOval() | Draws an oval | canvas.drawOval(rectF, paint) |
| drawArc() | Draws an arc or sector | canvas.drawArc(rectF, 0f, 90f, true, paint) |
Each method can be called in any order — Canvas draws in the order of calls, layering new elements on top of previous ones. For complex compositions, the multi-layer rendering technique with saving and restoring Canvas state is used.
Paint — is the class that defines how Canvas will draw. Without Paint, Canvas methods are useless — it sets color, line thickness, fill style, text size, effects (shadow, gradient, blur) and blending mode.
Paint has key properties: color, style (FILL, STROKE or FILL_AND_STROKE), strokeWidth, textSize, isAntiAlias, shader (gradient or image). Creating a Paint object is an expensive operation, so it should be created once and reused.
private val fillPaint = Paint().apply {
color = Color.parseColor("#FF6200EE")
style = Paint.Style.FILL
isAntiAlias = true
}
private val strokePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 4f
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(100f, 100f, 80f, fillPaint)
canvas.drawCircle(100f, 100f, 80f, strokePaint)
}
In this example, two Paint objects are used: one for filling the circle with purple color, the second for white stroke. Separating fill and stroke is a standard practice that allows flexible control of appearance without recreating objects.
For gradient fills, Paint accepts a Shader — subclasses LinearGradient, RadialGradient, SweepGradient. Shader is attached to Paint through the setShader() method and defines how color changes across the drawing area.
Canvas supports matrix transformations of the coordinate system: translate(), rotate(), scale() and skew(). Transformations accumulate: each subsequent call is applied on top of previous ones.
Managing the transformation stack is done through save() and restore(). save() saves the current Canvas state (matrix, clip) to the stack, restore() restores the last saved state. Between save() and restore(), nested transformations can be applied.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save() // Save initial state
canvas.translate(100f, 100f) // Translate origin
// First square
canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)
canvas.save() // Save after translate
canvas.rotate(45f, 25f, 25f) // Rotate around square center
// Rotated square
canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
canvas.restore() // Restore after rotate
canvas.restore() // Restore initial state
}
Always use save() before starting transformations and restore() after completing them. Unbalanced save/restore leads to matrix accumulation and incorrect rendering of subsequent elements.
For complex graphics, Canvas is combined with Bitmap and Path. Bitmap is used for loading images and caching rendered frames. Path is used for building arbitrary Bezier curves, polygons and complex contours.
The offscreen rendering technique: a Bitmap is created, a complex scene is drawn onto it through a separate Canvas, and then the finished Bitmap is output in onDraw() via drawBitmap(). This allows updating complex graphics without full redraw on each frame.
private var offscreenBitmap: Bitmap? = null
private var offscreenCanvas: Canvas? = null
fun buildOffscreenScene(width: Int, height: Int) {
offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
offscreenCanvas = Canvas(offscreenBitmap)
// Render scene once
offscreenCanvas!!.drawColor(Color.WHITE)
drawGrid(offscreenCanvas!!)
drawComplexShape(offscreenCanvas!!)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
offscreenBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}
Path is used for building complex contours: Bezier curves (quadTo, cubicTo), arcs (arcTo), lines (lineTo). Path can be closed (close()) for filled shapes or open for lines. The main drawPath() method combines Path and Paint for final rendering.
Canvas rendering performance directly affects application FPS. The main issues: excessive allocations in onDraw(), frequent invalidate() calls and suboptimal use of Hardware Acceleration.
Paint, Path, Rect, RectF and other objects should be created once in the initializer or setup() method. Each allocation in onDraw() creates garbage collector pressure, leading to micro-lags. The exception is static or companion object for immutable objects.
If most of the View is hidden outside the visible area (for example, in ScrollView), use canvas.clipRect() to limit rendering. Hardware Acceleration in this case additionally optimizes clipping of invisible pixels.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Clip to visible area
canvas.save()
canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)
// Render large scene
renderScene(canvas)
canvas.restore()
}
Use View.setLayerType() with LAYER_TYPE_HARDWARE for Views with complex Canvas graphics that update infrequently. For frequently updated animation, conversely, use LAYER_TYPE_NONE to avoid expensive hardware layer updates. Profile using GPU Profile Rendering and Android Studio Profiler.
Frequently Asked Questions
Yes, you can create Canvas(Bitmap) — a constructor that accepts a Bitmap. This is used for offscreen rendering: you draw on a Bitmap, and then output it via drawBitmap(). Canvas can also be created from Surface (for SurfaceView) or through Picture.
Canvas — is an API for drawing on View in the UI thread. SurfaceView — is a separate Surface that can be updated from a background thread through its own Canvas (lockCanvas() / unlockCanvasAndPost()). SurfaceView is preferred for games and video.
The most common reason is unbalanced save() and restore(). If after a transformation (translate, rotate) restore() is not called, the Canvas remains in a modified coordinate system, and subsequent elements are drawn with an offset. Check your save()/restore() pairs.
Use Paint.setTextAlign(Paint.Align.CENTER) for horizontal centering and Paint.getTextBounds() or Paint.descent() / ascent() for vertical. Centering: canvas.drawText(text, centerX, centerY - (ascent + descent) / 2, paint).
Canvas itself does not animate — it only draws the current state. Animation is achieved through sequential calls to invalidate() (or postInvalidateOnAnimation()), each time drawing a new frame in onDraw(). For smooth animations use ValueAnimator or Choreographer to sync with Vsync.
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