onDraw() — ключов метод на класа View в Android SDK, отговорен за рендиране на съдържанието на изгледа. Системата извиква onDraw при всяко обновяване на външния вид на View — при начално рендиране, след invalidate() или при промяна на размерите. Според Android Developers Documentation (2026), пренаписването на onDraw с използване на Canvas API е основният начин за създаване на персонализирана графика в Android приложения.
Основни точки
onDraw(Canvas canvas) — защитен метод на класа 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 нишка). Пълният цикъл се състои от три фази: measure (измерване на размери), layout (изчисляване на позиции) и draw (рендиране). onDraw е част от фазата draw и се извиква, след като размерите и позицията на View вече са определени.
draw(Canvas canvas) — публичен метод на 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също