onDraw() — a View osztály kulcsfontosságú metódusa az Android SDK-ban, amely a nézet tartalmának megjelenítéséért felelős. A rendszer az onDraw-ot hívja meg a View megjelenésének minden frissítésekor — a kezdeti rendereléskor, invalidate() után vagy a méretek megváltozásakor. Az Android Developers Documentation (2026) szerint az onDraw felülírása a Canvas API használatával az elsődleges módja az egyedi grafika létrehozásának Android alkalmazásokban.
Főbb pontok
onDraw(Canvas canvas) — az android.view.View osztály protected metódusa, amelyet az Android rendszer hív meg, amikor a View-t renderelni kell. A fejlesztő ezt a metódust írja felül az egyedi renderelés megvalósításához: alakzatok, szöveg, képek, gradiensek, animáció. A Canvas paraméter az a grafikus felület, amelyen az összes rajzolási művelet végrehajtódik.
A metódus szignatúrája egyszerű: override fun onDraw(canvas: Canvas). Ezen metóduson belül nem lehet új objektumokat létrehozni (a GC terhelés elkerülése érdekében), hosszú műveleteket végrehajtani vagy hálózattal dolgozni. A View szintén tartalmazza az onDraw metódust az életciklusában az onLayout (pozíciók kiszámítása) és a dispatchDraw (gyermek elemek renderelése) között.
A View-ból való származtatáskor az onDraw felülírása kötelező bármilyen tartalom megjelenítéséhez — ellenkező esetben a View üres lesz. Meglévő alosztályokból (TextView, ImageView, Button) való származtatáskor az onDraw felülírása opcionális, és a standard tartalom fölé további grafika hozzáadására szolgál.
Android a View renderelését szigorúan meghatározott sorrendben végzi, amelyet a fő szálból (UI thread) hív meg. A teljes ciklus három fázisból áll: measure (méretek mérése), layout (pozíciók kiszámítása) és draw (renderelés). Az onDraw a draw fázis része, és a View méreteinek és pozíciójának meghatározása után hívódik meg.
draw(Canvas canvas) — a View publikus metódusa, amely meghívja az onDraw-ot és a dispatchDraw-ot. A draw metódus nem felülírásra szolgál — helyette az onDraw-t kell felülírni. Az aktuális View onDraw-jának végrehajtása után a dispatchDraw hívódik meg, amely rekurzívan meghívja a draw-ot az összes gyermek View-ra (ha az aktuális View ViewGroup konténer).
onDraw a következő feltételek mellett hívódik meg: 1) a View először válik láthatóvá (setVisibility(VISIBLE) után); 2) az alkalmazás invalidate()-ot hív erre a View-ra vagy szülőjére; 3) a View mérete megváltozik (új layout); 4) a View-hoz kapcsolódó animáció megváltozik (ValueAnimator vagy ObjectAnimator révén); 5) a View állapota megváltozik (pressed, focused, enabled).
Android 3.0 (API 11) óta a legtöbb Canvas művelet hardveres gyorsítással, GPU-n keresztül történik. A fejlesztő ellenőrizheti, hogy a gyorsítás be van-e kapcsolva a canvas.isHardwareAccelerated() segítségével. Hardveres gyorsítás esetén egyes Canvas műveletek nem támogatottak (pl. clipPath nem négyszögletes területtel) és figyelmen kívül hagyhatók vagy hibát okozhatnak.
Canvas gazdag metóduskészletet biztosít a rajzoláshoz. Minden metódus az aktuális koordináta-rendszerben működik, amely transzformációkkal (translate, rotate, scale, skew) módosítható. Minden metódus egy Paint objektumot fogad, amely meghatározza a színt, stílust, vonalvastagságot, effekteket és él simítást.
| Canvas metódus | Cél | Paint kulcsparaméterei |
|---|---|---|
| drawCircle | Kör rajzolása | centerX, centerY, radius, paint |
| drawRect | Téglalap rajzolása | left, top, right, bottom, paint |
| drawLine | Vonal rajzolása | startX, startY, endX, endY, paint |
| drawPath | Tetszőleges kontúr rajzolása | path, paint |
| drawBitmap | Kép rajzolása | bitmap, srcRect, dstRect, paint |
| drawText | Szöveg rajzolása | text, x, y, paint |
| drawArc | Ív vagy szektor rajzolása | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Ovális rajzolása | ovalRect, paint |
Paint meghatározza a renderelési stílust: szín (setColor), vonalvastagság (setStrokeWidth), kitöltési stílus (setStyle — FILL, STROKE, FILL_AND_STROKE), él simítás (setAntiAlias), effektek (setShadowLayer, setMaskFilter) és gradiensek (setShader). Javasolt a Paint-et egyszer létrehozni és konfigurálni a View konstruktorában, nem az onDraw-on belül, hogy elkerüljük az objektumok létrehozását a renderelési ciklusban.
Canvas olyan koordináta-rendszert használ, ahol az X tengely jobbra, az Y tengely pedig lefelé mutat. A koordináta-rendszer kezdőpontja (0, 0) a View bal felső sarka. A draw metódusok meghívása előtt a fejlesztő módosíthatja a koordináta-rendszert a canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py) segítségével. Minden további rajzolási művelet a transzformált rendszerben fog végrehajtódni.
onDraw a fő UI szálon fut, és időtartama közvetlenül befolyásolja a képkocka sebességet (fps). A 60 fps fenntartásához az onDraw metódusnak legfeljebb 16 ezredmásodpercen belül be kell fejeződnie. Bármilyen lassulás képkocka kihagyáshoz (jank) vezet, ami vizuálisan az animáció akadozásaként vagy görgetés közbeni késésként érzékelhető.
Az alapvető szabályok az onDraw optimalizálásához a következők: 1) ne hozzon létre objektumokat az onDraw-on belül — minden Paint, Path, Rect osztály mező legyen; 2) ne foglaljon memóriát (new) — ez szemétgyűjtést (GC) és képkocka kihagyást vált ki; 3) gyorsítótárazza az összetett műveleteket Bitmap-ben a Bitmap.createBitmap segítségével, és rajzolja ki a kész Bitmap-et; 4) ne végezzen be-ki viteli műveleteket vagy erőforrás betöltést.
clipRect a rajzolási területet egy meghatározott téglalapra korlátozza. Arra szolgál, hogy a Canvas ne vesztegessen időt a látható területen kívüli pixelek renderelésére. Különösen hatékony nagy listák görgetésekor vagy méretezéskor: állítsa a clipRect-et a látható területre (dirty rect), és a Canvas csak azt rajzolja ki, ami látható.
canvas.save() elmenti a Canvas aktuális állapotát (transzformáció és clip) a verembe. canvas.restore() visszaállítja az utolsó mentett állapotot. Ez szükséges az ideiglenes transzformációkhoz: menti az állapotot, alkalmazza a translate/rotate-ot, rajzol, majd visszaállít. A save és restore könnyű, és ésszerű használat mellett (kevesebb mint 10–15 beágyazási szint) nem befolyásolja a teljesítményt.
invalidate() — a View metódusa, amely értesíti a rendszert, hogy a View tartalma elavult, és újra kell rajzolni. Az invalidate hívás egy új draw ciklust indít, amelyben az onDraw meghívásra kerül. Az invalidate-ot csak a UI szálból (main thread) szabad meghívni. Háttérszálból történő híváshoz a postInvalidate használatos.
postInvalidate() — az invalidate szálbiztos változata, amely az újrarajzolási kérést a Handleren keresztül a fő szál sorába küldi. Háttérfeladatokban használatos, amikor a View-t frissíteni kell az adatbetöltés vagy számítások befejezése után. A UI-val való szinkronizáláshoz runOnUiThread-tel kombinálják.
Részleges újrarajzoláshoz az invalidate(Rect dirty) vagy invalidate(int l, int t, int r, int b) használatos. Ez jelzi a rendszernek, hogy csak a megadott terület változott, nem a teljes View. Az Android optimalizálja a renderelést, csak a dirty rect-et rajzolja újra, ami jelentősen növeli a teljesítményt a részleges frissítésekkel rendelkező animációknál.
Nézzük meg gyakorlati példákat az onDraw felülírására egyedi View-k létrehozásához Android-ban Kotlin segítségével.
A példa egy kör alakú folyamatjelzőt hoz létre animált kitöltéssel. Az onDraw két kört rajzol: egy szürke hátteret és egy színes kitöltést, amelynek ívét az aktuális folyamat határozza meg. Az invalidate hívás a setProgress-ben biztosítja az újrarajzolást az érték változásakor.
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)
}
}
A példa egy gradienssel ellátott téglalapot rajzol szöveggel a tetején. A gradiens LinearGradient (Shader) segítségével, a szöveg pedig drawText segítségével jön létre. Mindkét Paint egyszer jön létre a konstruktorban, és az onDraw-ban csak a téglalap mérete változik a View aktuális szélességétől függően.
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("Helló a Canvas-tól",
xCenter, yCenter - yOffset,
textPaint)
}
}
A példa egy animált órát hoz létre, amely 1000 ezredmásodpercenként frissíti a renderelést. A postDelayed az onDraw-ban egy másodperc után meghívja az invalidate-ot, létrehozva egy végtelen újrarajzolási ciklust. Az óra mutatói a drawLine segítségével rajzolódnak ki különböző hosszúsággal és vastagsággal.
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)
}
}
Gyakran ismételt kérdések
Nem, az onDraw-ot nem szabad közvetlenül meghívni. Újrarajzolás kéréséhez használja az invalidate()-ot (UI szálból) vagy a postInvalidate()-ot (háttérszálból). A rendszer maga dönti el, mikor hívja meg az onDraw-ot, és optimalizálja a renderelést, több invalidate hívást egy menetbe egyesítve.
Okok: a View el van rejtve (setVisibility(GONE) vagy INVISIBLE); a View nincs hozzáadva a Window hierarchiához; a View mérete 0 (width vagy height = 0); setWillNotDraw(true) hívódik (alapértelmezés szerint true ViewGroup esetén). Hibakereséskor ellenőrizze ezeket a pontokat.
60 fps esetén az onDraw akár 60-szor is meghívódhat másodpercenként. 120 fps esetén (120 Hz-es kijelzővel rendelkező eszközökön) — akár 120-szor. Minden hívásnak ~8–16 ezredmásodpercen belül be kell fejeződnie. Aktív ObjectAnimator vagy ValueAnimator animációk esetén a gyakoriságot az animációs képkockák száma határozza meg.
onDraw a View saját tartalmának rendereléséért felelős. dispatchDraw a gyermek View-k rendereléséért felelős (ViewGroup-ban). A dispatchDraw az onDraw után hívódik, és rekurzívan bejárja az összes gyermek elemet. Egy szokványos View (nem ViewGroup) esetén a dispatchDraw üres.
Igen, a Canvas létrehozható Bitmap számára a Canvas(bitmap) segítségével, és bármely szálon lehet rá rajzolni. Ez az összetett grafika háttérben történő előzetes kiszámítására használatos. Az onDraw-ban átadott Canvas azonban a rendszeré, és csak a UI szálon belül, az onDraw metóduson belül használható.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is