onDraw() — este metoda cheie a clasei View din Android SDK, responsabilă pentru redarea conținutului vizualizării. Sistemul apelează onDraw la fiecare actualizare a aspectului View — la redarea inițială, după invalidate() sau la modificarea dimensiunilor. Conform Android Developers Documentation (2026), suprascrierea onDraw utilizând Canvas API este principala modalitate de a crea grafică personalizată în aplicațiile Android.
Principalele puncte
onDraw(Canvas canvas) — este o metodă protected a clasei android.view.View, care este apelată de sistemul Android atunci când View trebuie redat. Dezvoltatorul suprascrie această metodă pentru a implementa redarea personalizată: forme, text, imagini, gradiente, animații. Parametrul Canvas este suprafața grafică pe care se execută toate operațiile de desenare.
Semnătura metodei este simplă: override fun onDraw(canvas: Canvas). În interiorul acestei metode nu se pot crea obiecte noi (pentru a evita încărcarea GC), nu se pot executa operații lungi sau lucra cu rețeaua. View conține de asemenea metoda onDraw în ciclul său de viață între onLayout (calcularea pozițiilor) și dispatchDraw (redarea elementelor copil).
La moștenirea de la View, suprascrierea onDraw este obligatorie pentru afișarea oricărui conținut — altfel View va fi gol. La moștenirea de la subclase existente (TextView, ImageView, Button), suprascrierea onDraw este opțională și utilizată pentru adăugarea de grafică suplimentară peste conținutul standard.
Android execută redarea View într-o succesiune strict definită, apelată din firul principal (UI thread). Ciclul complet constă din trei faze: measure (măsurarea dimensiunilor), layout (calcularea pozițiilor) și draw (redarea). onDraw face parte din faza draw și este apelat după ce dimensiunile și poziția View au fost deja determinate.
draw(Canvas canvas) — este metoda publică a View care apelează onDraw și dispatchDraw. Metoda draw nu este destinată suprascrierii — în locul ei se suprascrie onDraw. După executarea onDraw pentru View-ul curent, este apelat dispatchDraw, care apelează recursiv draw pentru toate View-urile copil (dacă View-ul curent este un container ViewGroup).
onDraw este apelat în următoarele condiții: 1) View devine vizibil pentru prima dată (după setVisibility(VISIBLE)); 2) aplicația apelează invalidate() pentru acest View sau părintele său; 3) se modifică dimensiunea View (nou layout); 4) se modifică animația asociată View-ului (prin ValueAnimator sau ObjectAnimator); 5) se modifică starea View (pressed, focused, enabled).
Începând cu Android 3.0 (API 11), majoritatea operațiilor Canvas sunt executate cu accelerare hardware prin GPU. Dezvoltatorul poate verifica dacă accelerarea este activată prin canvas.isHardwareAccelerated(). La accelerarea hardware, unele operații Canvas nu sunt suportate (de exemplu, clipPath cu o zonă non-dreptunghiulară) și pot fi ignorate sau pot cauza erori.
Canvas oferă un set bogat de metode pentru desenare. Toate metodele funcționează în sistemul de coordonate curent, care poate fi modificat prin transformări (translate, rotate, scale, skew). Fiecare metodă primește un obiect Paint care definește culoarea, stilul, grosimea liniei, efectele și anti-aliasingul.
| Metoda Canvas | Destinație | Parametrii cheie Paint |
|---|---|---|
| drawCircle | Desenarea cercului | centerX, centerY, radius, paint |
| drawRect | Desenarea dreptunghiului | left, top, right, bottom, paint |
| drawLine | Desenarea liniei | startX, startY, endX, endY, paint |
| drawPath | Desenarea conturului arbitrar | path, paint |
| drawBitmap | Desenarea imaginii | bitmap, srcRect, dstRect, paint |
| drawText | Desenarea textului | text, x, y, paint |
| drawArc | Desenarea arcului sau sectorului | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Desenarea ovalului | ovalRect, paint |
Paint definește stilul de redare: culoarea (setColor), grosimea liniei (setStrokeWidth), stilul de umplere (setStyle — FILL, STROKE, FILL_AND_STROKE), anti-aliasingul (setAntiAlias), efectele (setShadowLayer, setMaskFilter) și gradientele (setShader). Se recomandă crearea și configurarea Paint o singură dată în constructorul View, nu în interiorul onDraw, pentru a evita crearea de obiecte în ciclul de redare.
Canvas utilizează un sistem de coordonate în care axa X este îndreptată spre dreapta, iar axa Y în jos. Originea coordonatelor (0, 0) este colțul stânga sus al View. Înainte de apelarea metodelor draw, dezvoltatorul poate modifica sistemul de coordonate prin canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). Toate operațiile de desenare ulterioare vor fi executate în sistemul transformat.
onDraw este executat în firul principal UI, iar durata sa influențează direct rata de cadre (fps). Pentru menținerea a 60 fps, metoda onDraw trebuie să se finalizeze în cel mult 16 milisecunde. Orice încetinire duce la omiterea cadrelor (jank), ceea ce este perceput vizual ca întreruperi ale animației sau întârziere la derulare.
Regulile principale de optimizare onDraw includ: 1) nu creați obiecte în interiorul onDraw — toate Paint, Path, Rect trebuie să fie câmpuri ale clasei; 2) nu alocați memorie (new) — aceasta declanșează colectarea gunoiului (GC) și omiterea cadrelor; 3) cache-uiți operațiile complexe în Bitmap prin Bitmap.createBitmap și desenați Bitmap-ul pregătit; 4) nu executați operații de intrare-ieșire sau încărcare de resurse.
clipRect limitează zona de desenare la un dreptunghi specificat. Este utilizat pentru ca Canvas să nu piardă timp redând pixeli în afara zonei vizibile. Este deosebit de eficient la derularea listelor mari sau la scalare: setați clipRect pe zona vizibilă (dirty rect), iar Canvas va desena doar ceea ce este vizibil.
canvas.save() salvează starea curentă a Canvas (transformarea și clip) în stivă. canvas.restore() restaurează ultima stare salvată. Acest lucru este necesar pentru transformări temporare: salvați starea, aplicați translate/rotate, desenați, apoi restaurați. save și restore sunt ușoare și nu afectează performanța la o utilizare rezonabilă (mai puțin de 10–15 niveluri de imbricare).
invalidate() — este metoda View care informează sistemul că conținutul View este învechit și trebuie redesenat. Apelarea invalidate inițiază un nou ciclu draw, în care va fi apelat onDraw. invalidate trebuie apelat doar din firul UI (main thread). Pentru apelarea din firul de fundal se utilizează postInvalidate.
postInvalidate() — versiunea thread-safe a invalidate, care trimite solicitarea de redesare în coada firului principal prin Handler. Este utilizată în sarcinile de fundal când trebuie actualizat View după finalizarea încărcării datelor sau a calculelor. Se combină cu runOnUiThread pentru sincronizarea cu UI.
Pentru redesarea parțială se utilizează invalidate(Rect dirty) sau invalidate(int l, int t, int r, int b). Aceasta indică sistemului că s-a modificat doar zona specificată, nu întregul View. Android optimizează redarea, redesenând doar dirty rect, ceea ce crește semnificativ performanța la animațiile cu actualizări parțiale.
Să analizăm exemple practice de suprascriere a onDraw pentru crearea de View-uri personalizate în Android cu Kotlin.
Exemplul creează un indicator de progres circular cu umplere animată. onDraw desenează două cercuri: un fundal gri și unul colorat de umplere, al cărui arc este determinat de progresul curent. Apelarea invalidate în setProgress asigură redesarea la modificarea valorii.
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)
}
}
Exemplul desenează un dreptunghi cu gradient și text deasupra acestuia. Gradientul este creat prin LinearGradient (Shader), iar textul prin drawText. Ambele Paint sunt create o singură dată în constructor, iar în onDraw se modifică doar dimensiunea dreptunghiului în funcție de lățimea curentă a 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("Salut de la Canvas",
xCenter, yCenter - yOffset,
textPaint)
}
}
Exemplul creează un ceas animat care actualizează redarea la fiecare 1000 de milisecunde. postDelayed în onDraw apelează invalidate după o secundă, creând un ciclu infinit de redesare. Acele ceasului sunt desenate prin drawLine cu lungime și grosime diferite.
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)
}
}
Întrebări frecvente
Nu, nu trebuie să apelați onDraw direct. Pentru solicitarea redesării utilizați invalidate() (din firul UI) sau postInvalidate() (din firul de fundal). Sistemul decide singur când să apeleze onDraw și optimizează redarea, combinând multiple invalidate într-o singură trecere.
Cauze: View este ascuns (setVisibility(GONE) sau INVISIBLE); View nu a fost adăugat în ierarhia Window; dimensiunea View este 0 (width sau height = 0); se apelează setWillNotDraw(true) (implicit true pentru ViewGroup). Verificați fiecare dintre aceste puncte la depanare.
La 60 fps, onDraw poate fi apelat de până la 60 de ori pe secundă. La 120 fps (pe dispozitive cu afișaje de 120 Hz) — de până la 120 de ori. Fiecare apel trebuie să se încadreze în ~8–16 milisecunde. La animațiile active prin ObjectAnimator sau ValueAnimator, frecvența este determinată de numărul de cadre ale animației.
onDraw răspunde de redarea conținutului propriu al View. dispatchDraw răspunde de redarea View-urilor copil (în ViewGroup). dispatchDraw este apelat după onDraw și traversează recursiv toate elementele copil. Pentru un View obișnuit (nu ViewGroup), dispatchDraw este gol.
Da, Canvas poate fi creat pentru Bitmap prin Canvas(bitmap) și se poate desena pe el în orice fir. Acest lucru este utilizat pentru precalcularea graficii complexe în fundal. Totuși, Canvas transmis în onDraw aparține sistemului și poate fi utilizat doar în firul UI în interiorul metodei onDraw.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și