onDraw() — що це, механізм виклику та перевизначення методу

Автор: IT Sectr Опубліковано: 2026-07-22 Час читання: 10 хв

onDraw() — це ключовий метод класу View в Android SDK, відповідальний за малювання вмісту подання. Система викликає onDraw при кожному оновленні зовнішнього вигляду View — під час початкового рендерингу, після invalidate() або при зміні розмірів. Згідно з Android Developers Documentation (2026), перевизначення onDraw з використанням Canvas API є основним способом створення кастомної графіки в Android-додатках.

Головне

  • onDraw(Canvas) — метод View, що викликається системою для малювання вмісту на Canvas
  • invalidate() — запит на перемалювання View, що призводить до виклику onDraw в наступному циклі кадра
  • Canvas API надає методи drawCircle, drawBitmap, drawText, drawPath та інші для малювання
  • Система Android виконує малювання в потоці UI, тому onDraw має бути легким та швидким
  • Hardware acceleration автоматично прискорює малювання через GPU, починаючи з Android 3.0 (API 11)

Що таке onDraw()?

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 є необов’язковим і використовується для додавання додаткової графіки поверх стандартного вмісту.

Цикл малювання View

Android виконує малювання View в суворо визначеній послідовності, що викликається з основного потоку (UI thread). Повний цикл складається з трьох фаз: measure (вимірювання розмірів), layout (розрахунок позицій) та draw (рендеринг). onDraw є частиною фази draw та викликається після того, як розміри та положення View вже визначені.

Фаза draw: dispatchDraw та onDraw

draw(Canvas canvas) — це публічний метод View, який викликає onDraw та dispatchDraw. Метод draw не призначений для перевизначення — натомісь перевизначається onDraw. Після виконання onDraw для поточного View викликається dispatchDraw, який рекурсивно викликає draw для всіх дочірніх View (якщо поточний View є контейнером ViewGroup).

Коли система викликає onDraw

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 API для кастомної графіки

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

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

onDraw виконується в основному потоці UI, і його тривалість безпосередньо впливає на частоту кадрів (fps). Для підтримки 60 fps метод onDraw має завершуватися не більше ніж за 16 мілісекунд. Будь-яке уповільнення призводить до пропуску кадрів (jank), що візуально сприймається як заїкання анімації або затримка при прокруці.

Правила продуктивного onDraw

Основні правила оптимізації onDraw включають: 1) не створювати об’єкти всередині onDraw — усі об’єкти Paint, Path, Rect мають бути полями класу; 2) не виділяти пам’ять (new) — це викликає збирання сміття (GC) та пропуск кадрів; 3) кешувати складні операції в Bitmap через Bitmap.createBitmap та малювати готовий Bitmap; 4) не виконувати операцій вводу/виведення або завантаження ресурсів.

Використання clipRect для оптимізації

clipRect обмежує область малювання заданим прямокутником. Використовується для того, щоб Canvas не витрачав час на малювання пікселів за межами видимої області. Особливо ефективний при прокруці великих списків або масштабуванні: встановіть clipRect на видиму область (dirty rect), і Canvas малюватиме лише те, що видно.

Layer — збереження та відновлення

canvas.save() зберігає поточний стан Canvas (трансформацію та clip) в стек. canvas.restore() відновлює останній збережений стан. Це необхідно для тимчасових трансформацій: зберігаєте стан, застосовуєте translate/rotate, малюєте, потім відновлюєте. save та restore легковагі і не впливають на продуктивність при розумному використанні (менше 10–15 рівнів вкладеності).

invalidate та postInvalidate

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, що значно підвищує продуктивність анімацій з частковими оновленнями.

Приклади коду на Kotlin

Розглянемо практичні приклади перевизначення onDraw для створення кастомних View в Android на Kotlin.

Кастомний View — круговий прогрес

Приклад створює круговий індикатор прогресу з анімованим заповненням. onDraw малює два кола: сіре фонове та кольорове заповнюване, дуга якого визначається поточним прогресом. Виклик invalidate в setProgress забезпечує перемалювання при зміні значення.

kotlin
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)
    }
}

Кастомний View — градієнтний фон з текстом

Приклад малює прямокутник з градієнтом та текст поверх нього. Градієнт створюється через LinearGradient (Shader), а текст — через drawText. Обидва Paint створюються одноразово в конструкторі, а в onDraw лише змінюється розмір прямокутника відповідно до поточної ширини View.

kotlin
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)
    }
}

Анімований годинник з використанням invalidate

Приклад створює анімований годинник, що оновлює малювання кожні 1000 мілісекунд. postDelayed в onDraw викликає invalidate через секунду, створюючи нескінченний цикл перемалювання. Стрілки годинника малюються через drawLine з різною довжиною та товщиною.

kotlin
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 безпосередньо?

Ні, не слід викликати onDraw безпосередньо. Для запиту на перемалювання використовуйте invalidate() (з UI-потоку) або postInvalidate() (з фонового потоку). Система сама вирішить, коли викликати onDraw, і оптимізує малювання, об’єднуючи множинні invalidate в один прохід.

Чому onDraw не викликається після invalidate?

Причини: View приховано (setVisibility(GONE) або INVISIBLE); View не було додано в ієрархію Window; розмір View дорівнює 0 (width або height = 0); викликається setWillNotDraw(true) (за замовчуванням true для ViewGroup). Перевірте кожний з цих пунктів при налагодженні.

Як часто може викликатися onDraw?

При 60 fps onDraw може викликатися до 60 разів на секунду. При 120 fps (на пристроях з 120 Гц дисплеями) — до 120 разів. Кожен виклик має завершуватися приблизно за 8–16 мілісекунд. Під час активних анімацій через ObjectAnimator або ValueAnimator частота визначається кількістю кадрів анімації.

В чому різниця між onDraw та dispatchDraw?

onDraw відповідає за малювання вмісту самого View. dispatchDraw відповідає за малювання дочірніх View (в ViewGroup). dispatchDraw викликається після onDraw та рекурсивно проходить по всіх дочірніх елементах. Для звичайного View (не ViewGroup) dispatchDraw порожній.

Чи можна використовувати Canvas поза onDraw?

Так, Canvas можна створювати для Bitmap через Canvas(bitmap) та малювати на ньому в будь-якому потоці. Це використовується для попереднього розрахунку складної графіки в фоні. Однак Canvas, що передається в onDraw, належить системі та може використовуватися лише в UI-потоці всередині методу onDraw.

Підсумки

  • onDraw(Canvas) — ключовий метод View для кастомного малювання з використанням Canvas API в Android
  • Цикл малювання складається з фаз measure, layout та draw — onDraw викликається на фазі draw
  • Canvas API включає методи drawCircle, drawRect, drawPath, drawBitmap, drawText та інші
  • invalidate() запитує перемалювання з UI-потоку, postInvalidate() — з фонового потоку
  • Часткове перемалювання через invalidate(Rect) оптимізує рендеринг, оновлюючи лише dirty-область
  • Продуктивність onDraw критична: створення об’єктів всередині методу викликає GC та пропуск кадрів
  • Апаратне прискорення (з API 11) автоматично прискорює операції Canvas через GPU

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також