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) — це публічний метод 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-потоку (основного потоку). Для виклику з фонового потоку використовується 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("Hello from 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 Гц дисплеями) — до 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також