onDraw() è un metodo chiave della classe View nell’Android SDK responsabile del disegno del contenuto di una vista. Il sistema chiama onDraw a ogni aggiornamento dell’aspetto della View — durante il rendering iniziale, dopo invalidate() o quando le dimensioni cambiano. Secondo la documentazione di Android Developers (2026), l’override di onDraw utilizzando l’API Canvas è il modo principale per creare grafica personalizzata nelle applicazioni Android.
Punti chiave
onDraw(Canvas canvas) è un metodo protected della classe android.view.View, che il sistema Android chiama quando una View deve essere disegnata. Lo sviluppatore sovrascrive questo metodo per implementare il disegno personalizzato: forme, testo, immagini, gradienti, animazioni. Il parametro Canvas è una superficie grafica su cui vengono eseguite tutte le operazioni di disegno.
La firma del metodo è semplice: override fun onDraw(canvas: Canvas). All’interno di questo metodo, non si devono creare nuovi oggetti (per evitare il carico del GC), eseguire operazioni lunghe o lavorare con la rete. View include anche il metodo onDraw nel suo ciclo di vita tra onLayout (calcolo delle posizioni) e dispatchDraw (disegno degli elementi figli).
Quando si eredita da View, l’override di onDraw è obbligatorio per visualizzare qualsiasi contenuto — altrimenti la View sarà vuota. Quando si eredita da sottoclassi esistenti (TextView, ImageView, Button), l’override di onDraw è opzionale e viene utilizzato per aggiungere grafica aggiuntiva sopra il contenuto standard.
Android esegue il disegno della View in una sequenza rigorosamente definita chiamata dal thread principale (UI thread). Il ciclo completo consiste in tre fasi: measure (misurazione delle dimensioni), layout (calcolo delle posizioni) e draw (rendering). onDraw fa parte della fase draw e viene chiamato dopo che le dimensioni e la posizione della View sono state determinate.
draw(Canvas canvas) è un metodo pubblico di View che chiama onDraw e dispatchDraw. Il metodo draw non è progettato per essere sovrascritto — invece, viene sovrascritto onDraw. Dopo l’esecuzione di onDraw per la View corrente, viene chiamato dispatchDraw, che invoca ricorsivamente draw per tutte le View figlie (se la View corrente è un contenitore ViewGroup).
onDraw viene chiamato nelle seguenti condizioni: 1) la View diventa visibile per la prima volta (dopo setVisibility(VISIBLE)); 2) l’app chiama invalidate() su questa View o sul suo genitore; 3) la dimensione della View cambia (nuovo layout); 4) un’animazione associata alla View cambia (tramite ValueAnimator o ObjectAnimator); 5) lo stato della View cambia (pressed, focused, enabled).
Da Android 3.0 (API 11), la maggior parte delle operazioni Canvas sono accelerate via hardware attraverso la GPU. Lo sviluppatore può verificare se l’accelerazione è abilitata tramite canvas.isHardwareAccelerated(). Con l’accelerazione hardware, alcune operazioni Canvas non sono supportate (ad esempio, clipPath con una regione non rettangolare) e possono essere ignorate o causare errori.
Canvas fornisce un ricco insieme di metodi di disegno. Tutti i metodi funzionano nel sistema di coordinate corrente, che può essere modificato attraverso trasformazioni (translate, rotate, scale, skew). Ogni metodo accetta un oggetto Paint che definisce colore, stile, spessore della linea, effetti e anti-aliasing.
| Metodo Canvas | Scopo | Parametri chiave di Paint |
|---|---|---|
| drawCircle | Disegnare un cerchio | centerX, centerY, radius, paint |
| drawRect | Disegnare un rettangolo | left, top, right, bottom, paint |
| drawLine | Disegnare una linea | startX, startY, endX, endY, paint |
| drawPath | Disegnare un percorso arbitrario | path, paint |
| drawBitmap | Disegnare un’immagine bitmap | bitmap, srcRect, dstRect, paint |
| drawText | Disegnare testo | text, x, y, paint |
| drawArc | Disegnare un arco o settore | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Disegnare un ovale | ovalRect, paint |
Paint definisce lo stile di disegno: colore (setColor), spessore della linea (setStrokeWidth), stile di riempimento (setStyle — FILL, STROKE, FILL_AND_STROKE), anti-aliasing (setAntiAlias), effetti (setShadowLayer, setMaskFilter) e gradienti (setShader). Si consiglia di creare e configurare Paint una volta nel costruttore della View, non all’interno di onDraw, per evitare la creazione di oggetti nel ciclo di disegno.
Canvas utilizza un sistema di coordinate in cui l’asse X punta a destra e l’asse Y verso il basso. L’origine (0, 0) è l’angolo superiore sinistro della View. Prima di chiamare i metodi di disegno, lo sviluppatore può modificare il sistema di coordinate tramite canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). Tutte le operazioni di disegno successive verranno eseguite nel sistema trasformato.
onDraw viene eseguito nel thread principale UI e la sua durata influisce direttamente sul frame rate (fps). Per mantenere 60 fps, il metodo onDraw deve essere completato entro 16 millisecondi. Qualsiasi rallentamento porta a cadute di frame (jank), che visivamente appaiono come balbuzie dell’animazione o ritardo nello scorrimento.
Regole principali per l’ottimizzazione di onDraw includono: 1) non creare oggetti all’interno di onDraw — tutti gli oggetti Paint, Path, Rect devono essere campi della classe; 2) non allocare memoria (new) — questo attiva la garbage collection (GC) e cadute di frame; 3) memorizzare nella cache le operazioni complesse in un Bitmap tramite Bitmap.createBitmap e disegnare il Bitmap preparato; 4) non eseguire operazioni di I/O o caricamento di risorse.
clipRect limita l’area di disegno a un rettangolo specificato. Viene utilizzato per evitare che Canvas perda tempo a disegnare pixel fuori dall’area visibile. Particolarmente efficace durante lo scorrimento di grandi elenchi o lo zoom: imposta clipRect sull’area visibile (dirty rect) e Canvas disegnerà solo ciò che è visibile.
canvas.save() salva lo stato corrente del Canvas (trasformazione e clip) su uno stack. canvas.restore() ripristina l’ultimo stato salvato. Necessario per trasformazioni temporanee: salva lo stato, applica translate/rotate, disegna, quindi ripristina. save e restore sono leggeri e non influiscono sulle prestazioni se usati ragionevolmente (meno di 10–15 livelli di annidamento).
invalidate() è un metodo di View che informa il sistema che il contenuto della View è obsoleto e deve essere ridisegnato. Chiamare invalidate avvia un nuovo ciclo di draw in cui verrà chiamato onDraw. invalidate deve essere chiamato solo dal thread UI (thread principale). Per chiamarlo da un thread in background, si usa postInvalidate.
postInvalidate() è una versione thread-safe di invalidate che invia una richiesta di ridisegno alla coda del thread principale tramite Handler. Viene utilizzata in attività di background quando la View deve essere aggiornata dopo il completamento del caricamento dei dati o dei calcoli. Può essere combinata con runOnUiThread per la sincronizzazione con l’UI.
Per il ridisegno parziale, utilizzare invalidate(Rect dirty) o invalidate(int l, int t, int r, int b). Questo indica al sistema che solo l’area specificata è cambiata, non l’intera View. Android ottimizza il disegno ridisegnando solo il dirty rect, migliorando significativamente le prestazioni per le animazioni con aggiornamenti parziali.
Vediamo esempi pratici di override di onDraw per creare View personalizzate in Android usando Kotlin.
Questo esempio crea un indicatore di progresso circolare con riempimento animato. onDraw disegna due cerchi: un cerchio di sfondo grigio e un cerchio di riempimento colorato il cui arco è determinato dal progresso corrente. La chiamata a invalidate in setProgress garantisce il ridisegno quando il valore cambia.
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)
}
}
Questo esempio disegna un rettangolo con sfumatura e testo sopra di esso. La sfumatura viene creata tramite LinearGradient (Shader) e il testo tramite drawText. Entrambi gli oggetti Paint vengono creati una volta nel costruttore, mentre onDraw aggiorna solo la dimensione del rettangolo in base alla larghezza corrente della View.
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)
}
}
Questo esempio crea un orologio animato che aggiorna il disegno ogni 1000 millisecondi. postDelayed all’interno di onDraw chiama invalidate dopo un secondo, creando un ciclo di ridisegno infinito. Le lancette dell’orologio vengono disegnate tramite drawLine con diverse lunghezze e spessori.
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)
}
}
Domande frequenti
No, non si deve chiamare onDraw direttamente. Per richiedere un ridisegno, utilizzare invalidate() (dal thread UI) o postInvalidate() (da un thread in background). Il sistema deciderà quando chiamare onDraw e ottimizzerà il disegno unendo più chiamate invalidate in un unico passaggio.
Ragioni: la View è nascosta (setVisibility(GONE) o INVISIBLE); la View non è stata aggiunta alla gerarchia della Window; la dimensione della View è 0 (larghezza o altezza = 0); viene chiamato setWillNotDraw(true) (true per impostazione predefinita per ViewGroup). Controllare ciascuno di questi punti durante il debug.
A 60 fps, onDraw può essere chiamato fino a 60 volte al secondo. A 120 fps (su dispositivi con display a 120 Hz) — fino a 120 volte. Ogni chiamata deve essere completata entro circa 8–16 millisecondi. Durante le animazioni attive tramite ObjectAnimator o ValueAnimator, la frequenza è determinata dal numero di frame dell’animazione.
onDraw è responsabile del disegno del contenuto della View stessa. dispatchDraw è responsabile del disegno delle View figlie (in ViewGroup). dispatchDraw viene chiamato dopo onDraw e attraversa ricorsivamente tutti gli elementi figli. Per una View normale (non ViewGroup), dispatchDraw è vuoto.
Sì, è possibile creare un Canvas per un Bitmap tramite Canvas(bitmap) e disegnarci sopra in qualsiasi thread. Viene utilizzato per pre-rendering di grafica complessa in background. Tuttavia, il Canvas passato a onDraw appartiene al sistema e può essere utilizzato solo sul thread UI all’interno del metodo onDraw.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche