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