Canvas en Android — es una clase del paquete android.graphics que proporciona una API para renderizado 2D en View. Canvas funciona como un lienzo en el que el desarrollador dibuja formas geométricas, texto, imágenes y trazados mediante varios métodos draw*. Según la Documentación para Desarrolladores de Android (2025), Canvas es la herramienta principal para el renderizado personalizado en Android y se utiliza junto con la clase Paint para definir estilos y colores. Cada llamada a onDraw() recibe una instancia de Canvas ya preparada, asociada con el búfer de mapa de bits de la vista.
Puntos Clave
Canvas — es la clase central de gráficos 2D en Android que proporciona métodos para dibujar en un plano. Cada Canvas está vinculado a un Bitmap — un área de memoria donde se registran los resultados de las operaciones de dibujo. Cuando una View se redibuja, el sistema pasa a onDraw() un Canvas ya asociado con el búfer de esa View.
Canvas funciona en un sistema de coordenadas donde (0,0) es la esquina superior izquierda, el eje X va hacia la derecha, el eje Y hacia abajo. Todas las operaciones de dibujo usan el estado actual de Canvas, incluyendo la matriz de transformación, el área de recorte y el estilo de Paint. Según la Documentación de Android (2025), la API de Canvas incluye más de 30 métodos draw* para diferentes tipos de primitivas gráficas.
Desde Android 3.0 (API 11) Canvas admite aceleración por hardware a través de DisplayList. En modo acelerado, las operaciones de Canvas se registran en una DisplayList y luego se ejecutan en la GPU. Esto proporciona un aumento significativo de rendimiento pero impone limitaciones: algunas operaciones de Canvas (clipPath con clip no Path) no son compatibles en modo acelerado.
Canvas proporciona un conjunto de métodos para dibujar primitivas básicas. Cada método acepta parámetros geométricos y un objeto Paint que define el estilo visual.
| Método | Propósito | Ejemplo |
|---|---|---|
| drawRect() | Dibuja un rectángulo | canvas.drawRect(10f, 10f, 100f, 100f, paint) |
| drawCircle() | Dibuja un círculo | canvas.drawCircle(50f, 50f, 30f, paint) |
| drawLine() | Dibuja una línea | canvas.drawLine(0f, 0f, 100f, 100f, paint) |
| drawText() | Dibuja texto | canvas.drawText(“Hello”, 10f, 20f, paint) |
| drawBitmap() | Dibuja una imagen | canvas.drawBitmap(bitmap, 0f, 0f, paint) |
| drawPath() | Dibuja un trazado arbitrario | canvas.drawPath(path, paint) |
| drawOval() | Dibuja un óvalo | canvas.drawOval(rectF, paint) |
| drawArc() | Dibuja un arco o sector | canvas.drawArc(rectF, 0f, 90f, true, paint) |
Cada método puede llamarse en cualquier orden — Canvas dibuja en el orden de las llamadas, superponiendo nuevos elementos sobre los anteriores. Para composiciones complejas, se utiliza la técnica de renderizado multicapa con guardado y restauración del estado de Canvas.
Paint — es la clase que define cómo Canvas dibujará. Sin Paint, los métodos de Canvas son inútiles — establece color, grosor de línea, estilo de relleno, tamaño de texto, efectos (sombra, degradado, desenfoque) y modo de mezcla.
Paint tiene propiedades clave: color, style (FILL, STROKE o FILL_AND_STROKE), strokeWidth, textSize, isAntiAlias, shader (degradado o imagen). Crear un objeto Paint es una operación costosa, por lo que debe crearse una vez y reutilizarse.
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)
}
En este ejemplo, se usan dos objetos Paint: uno para rellenar el círculo con color púrpura, el segundo para el trazo blanco. Separar relleno y trazo es una práctica estándar que permite un control flexible de la apariencia sin recrear objetos.
Para rellenos degradados, Paint acepta un Shader — subclases LinearGradient, RadialGradient, SweepGradient. Shader se vincula a Paint mediante el método setShader() y define cómo cambia el color en el área de dibujo.
Canvas admite transformaciones matriciales del sistema de coordenadas: translate(), rotate(), scale() y skew(). Las transformaciones se acumulan: cada llamada posterior se aplica sobre las anteriores.
La gestión de la pila de transformaciones se realiza mediante save() y restore(). save() guarda el estado actual de Canvas (matriz, recorte) en la pila, restore() restaura el último estado guardado. Entre save() y restore() se pueden aplicar transformaciones anidadas.
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
}
Use siempre save() antes de comenzar las transformaciones y restore() después de completarlas. Los pares save/restore desbalanceados provocan acumulación de matriz y renderizado incorrecto de elementos posteriores.
Para gráficos complejos, Canvas se combina con Bitmap y Path. Bitmap se usa para cargar imágenes y almacenar en caché fotogramas renderizados. Path se usa para construir curvas Bezier arbitrarias, polígonos y contornos complejos.
La técnica de renderizado fuera de pantalla: se crea un Bitmap, se dibuja una escena compleja en él a través de un Canvas separado, y luego el Bitmap terminado se muestra en onDraw() mediante drawBitmap(). Esto permite actualizar gráficos complejos sin redibujar completamente en cada fotograma.
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 se usa para construir contornos complejos: curvas Bezier (quadTo, cubicTo), arcos (arcTo), líneas (lineTo). Path puede estar cerrado (close()) para formas rellenas o abierto para líneas. El método principal drawPath() combina Path y Paint para el renderizado final.
El rendimiento del renderizado de Canvas afecta directamente los FPS de la aplicación. Los principales problemas: asignaciones excesivas en onDraw(), llamadas frecuentes a invalidate() y uso subóptimo de la Aceleración por Hardware.
Paint, Path, Rect, RectF y otros objetos deben crearse una vez en el inicializador o método setup(). Cada asignación en onDraw() genera presión en el recolector de basura, lo que provoca microrretrasos. La excepción son static o companion object para objetos inmutables.
Si la mayor parte de la View está oculta fuera del área visible (por ejemplo, en ScrollView), use canvas.clipRect() para limitar el renderizado. La Aceleración por Hardware en este caso optimiza adicionalmente el recorte de píxeles invisibles.
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() con LAYER_TYPE_HARDWARE para Views con gráficos Canvas complejos que se actualizan con poca frecuencia. Para animaciones actualizadas con frecuencia, por el contrario, use LAYER_TYPE_NONE para evitar costosas actualizaciones de la capa de hardware. Perfile usando GPU Profile Rendering y Android Studio Profiler.
Preguntas Frecuentes
Sí, se puede crear Canvas(Bitmap) — un constructor que acepta un Bitmap. Esto se usa para renderizado fuera de pantalla: dibuja en un Bitmap y luego lo muestra mediante drawBitmap(). Canvas también puede crearse desde Surface (para SurfaceView) o mediante Picture.
Canvas — es una API para dibujar en View en el hilo de UI. SurfaceView — es un Surface separado que puede actualizarse desde un hilo en segundo plano a través de su propio Canvas (lockCanvas() / unlockCanvasAndPost()). SurfaceView es preferible para juegos y video.
La razón más común son los pares desbalanceados de save() y restore(). Si después de una transformación (translate, rotate) no se llama a restore(), Canvas permanece en un sistema de coordenadas modificado y los elementos posteriores se dibujan con desplazamiento. Verifique sus pares save()/restore().
Use Paint.setTextAlign(Paint.Align.CENTER) para centrado horizontal y Paint.getTextBounds() o Paint.descent() / ascent() para vertical. Centrado: canvas.drawText(texto, centerX, centerY - (ascent + descent) / 2, paint).
Canvas por sí mismo no anima — solo dibuja el estado actual. La animación se logra mediante llamadas secuenciales a invalidate() (o postInvalidateOnAnimation()), dibujando cada vez un nuevo fotograma en onDraw(). Para animaciones suaves use ValueAnimator o Choreographer para sincronizar con Vsync.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también