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-потока (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("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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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