onDraw() — то је кључни метод класе View у Android SDK-у, одговоран за цртање садржаја приказа. Систем позива onDraw при сваком ажурирању изгледа View-а — при почетном рендеровању, након invalidate() или при промени величина. Према Android Developers Documentation (2026), преоптерећивање onDraw-а коришћењем Canvas API-ја је основни начин креирања прилагођене графике у Android апликацијама.
Главно
onDraw(Canvas canvas) — то је protected метод класе android.view.View, који позива систем Android када View треба да буде рендерован. Програмер преоптерећује овај метод да би имплементирао прилагођено цртање: облике, текст, слике, градијенте, анимацију. Параметар Canvas је графичка површина на којој се извршавају све операције цртања.
Потпис метода је једноставан: override fun onDraw(canvas: Canvas). Унутар овог метода не могу се креирати нови објекти (да би се избегло оптерећење GC-а), извршавати дуге операције или радити са мрежом. View такође садржи метод onDraw у свом животном циклусу између onLayout (израчунавање позиција) и dispatchDraw (цртање дечијих елемената).
При наслеђивању од View-а преоптерећивање onDraw-а је обавезно за приказ било ког садржаја — у супротном View ће бити празан. При наслеђивању од постојећих подкласа (TextView, ImageView, Button) преоптерећивање onDraw-а је опционално и користи се за додавање додатне графике преко стандардног садржаја.
Android извршава цртање View-а у строго одређеном редоследу, који се позива из главне нити (UI thread). Пуни циклус се састоји од три фазе: measure (мерење величина), layout (израчунавање позиција) и draw (цртање). onDraw је део фазе draw и позива се након што су величине и положај View-а већ одређени.
draw(Canvas canvas) — то је public метод View-а који позива onDraw и dispatchDraw. Метод draw није намењен за преоптерећивање — уместо њега се преоптерећује onDraw. Након извршења onDraw-а за тренутни View, позива се dispatchDraw, који рекурзивно позива draw за све дечије View-ове (ако је тренутни View контејнер ViewGroup).
onDraw се позива под следећим условима: 1) View по први пут постаје видљив (након setVisibility(VISIBLE)); 2) апликација позива invalidate() за овај View или његовог родитеља; 3) мења се величина View-а (нови layout); 4) мења се анимација повезана са View-ом (кроз ValueAnimator или ObjectAnimator); 5) мења се стање View-а (pressed, focused, enabled).
Од Android 3.0 (API 11) већина Canvas операција се извршава са хардверским убрзањем кроз GPU. Програмер може проверити да ли је убрзање укључено кроз canvas.isHardwareAccelerated(). При хардверском убрзању неке Canvas операције нису подржане (нпр. clipPath са неправоугаоном облашћу) и могу бити игнорисане или изазвати грешке.
Canvas пружа богат скуп метода за цртање. Све методе раде у тренутном координатном систему, који се може мењати кроз трансформације (translate, rotate, scale, skew). Сваки метод прима објекат Paint који одређује боју, стил, дебљину линије, ефекте и изглађивање.
| Метод Canvas-а | Намена | Кључни параметри Paint-а |
|---|---|---|
| drawCircle | Цртање круга | centerX, centerY, radius, paint |
| drawRect | Цртање правоугаоника | left, top, right, bottom, paint |
| drawLine | Цртање линије | startX, startY, endX, endY, paint |
| drawPath | Цртање произвољног контура | path, paint |
| drawBitmap | Цртање слике | bitmap, srcRect, dstRect, paint |
| drawText | Цртање текста | text, x, y, paint |
| drawArc | Цртање лука или сектора | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Цртање овала | ovalRect, paint |
Paint одређује стил цртања: боју (setColor), дебљину линије (setStrokeWidth), стил попуне (setStyle — FILL, STROKE, FILL_AND_STROKE), изглађивање (setAntiAlias), ефекте (setShadowLayer, setMaskFilter) и градијенте (setShader). Препоручује се креирање и подешавање Paint-а једном у конструктору View-а, а не унутар onDraw-а, како би се избегло креирање објеката у циклусу цртања.
Canvas користи координатни систем где је оса X усмерена надесно, а оса Y наниже. Почетак координата (0, 0) је горњи леви угао View-а. Пре позива метода draw, програмер може променити координатни систем кроз canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). Све наредне операције цртања ће се извршавати у трансформисаном систему.
onDraw се извршава у главној UI нити, а његово трајање директно утиче на број кадрова у секунди (fps). За одржавање 60 fps, метод onDraw мора да се заврши за највише 16 милисекунди. Било какво успоравање доводи до прескакања кадрова (jank), што се визуелно доживљава као замуцкивање анимације или кашњење при скроловању.
Основна правила оптимизације onDraw-а укључују: 1) не креирати објекте унутар onDraw-а — сви Paint, Path, Rect треба да буду поља класе; 2) не алоцирати меморију (new) — то покреће сакупљач отпада (GC) и прескакање кадрова; 3) кеширати сложене операције у Bitmap кроз Bitmap.createBitmap и цртати готов Bitmap; 4) не извршавати операције улаза-излаза или учитавање ресурса.
clipRect ограничава област цртања на задати правоугаоник. Користи се да Canvas не би трошио време на рендеровање пиксела изван видљиве области. Посебно ефикасан при скроловању великих листи или скалирању: поставите clipRect на видљиву област (dirty rect), и Canvas ће цртати само оно што је видљиво.
canvas.save() чува тренутно стање Canvas-а (трансформацију и clip) на стек. canvas.restore() враћа последње сачувано стање. Ово је неопходно за привремене трансформације: сачувате стање, примените translate/rotate, цртате, затим враћате. save и restore су лагани и не утичу на перформансе при разумној употреби (мање од 10–15 нивоа угњежђења).
invalidate() — то је метод View-а који обавештава систем да је садржај View-а застарео и да га треба поново нацртати. Позив invalidate покреће нови циклус draw у којем ће бити позван onDraw. invalidate треба позивати само из UI нити (main thread). За позив из позадинске нити користи се postInvalidate.
postInvalidate() — нитно безбедна верзија invalidate-а, која шаље захтев за прецртавање у ред главне нити преко Handler-а. Користи се у позадинским задацима када треба ажурирати View након завршетка учитавања података или израчунавања. Комбинује се са runOnUiThread за синхронизацију са UI-јем.
За делимично прецртавање користи се invalidate(Rect dirty) или invalidate(int l, int t, int r, int b). Ово указује систему да се променила само одређена област, а не цео View. Android оптимизује рендеровање, прецртавајући само dirty rect, што значајно повећава перформансе при анимацијама са делимичним ажурирањима.
Размотримо практичне примере преоптерећивања onDraw-а за креирање прилагођених View-ова у Android-у на Kotlin-у.
Пример креира кружни индикатор напретка са анимираним попуњавањем. onDraw црта два круга: сиву позадину и обојени попуњени, чији лук одређује тренутни напредак. Позив invalidate у setProgress обезбеђује прецртавање при промени вредности.
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)
}
}
Пример црта правоугаоник са градијентом и текстом преко њега. Градијент се креира кроз LinearGradient (Shader), а текст кроз drawText. Оба Paint-а се креирају једном у конструктору, а у onDraw-у се само мења величина правоугаоника у зависности од тренутне ширине 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("Здраво из Canvas-а",
xCenter, yCenter - yOffset,
textPaint)
}
}
Пример креира анимирани сат који ажурира цртање сваких 1000 милисекунди. postDelayed у onDraw-у позива invalidate након секунде, стварајући бесконачни циклус прецртавања. Казаљке сата се цртају кроз drawLine са различитом дужином и дебљином.
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)
}
}
Често постављана питања
Не, не треба позивати onDraw директно. За захтев за прецртавање користите invalidate() (из UI нити) или postInvalidate() (из позадинске нити). Систем сам одлучује када да позове onDraw и оптимизује рендеровање, обједињујући вишеструке invalidate у један пролаз.
Разлози: View је сакривен (setVisibility(GONE) или INVISIBLE); View није додат у хијерархију Window-а; величина View-а је 0 (width или height = 0); позива се setWillNotDraw(true) (подразумевано true за ViewGroup). Проверите сваку од ових ставки при отклањању грешака.
При 60 fps, onDraw се може позивати до 60 пута у секунди. При 120 fps (на уређајима са дисплејима од 120 Hz) — до 120 пута. Сваки позив треба да се заврши за ~8–16 милисекунди. При активним анимацијама кроз ObjectAnimator или ValueAnimator, учесталост се одређује бројем кадрова анимације.
onDraw је одговоран за цртање садржаја самог View-а. dispatchDraw је одговоран за цртање дечијих View-ова (у ViewGroup-у). dispatchDraw се позива након onDraw-а и рекурзивно пролази кроз све дечије елементе. За обичан View (не ViewGroup) dispatchDraw је празан.
Да, Canvas се може креирати за Bitmap кроз Canvas(bitmap) и цртати на њему у било којој нити. Ово се користи за претходно израчунавање сложене графике у позадини. Међутим, Canvas који се прослеђује у onDraw припада систему и може се користити само у UI нити унутар метода onDraw.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође