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) — защитен метод на класа 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 нишка). Пълният цикъл се състои от три фази: 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 нишката (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, което значително подобрява производителността при анимации с частични актуализации.

Примери за код в 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("Здравейте от 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 Hz дисплеи) — до 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също