Canvas в Android — это класс из пакета android.graphics, предоставляющий API для 2D-отрисовки на View. Canvas работает как холст, на котором разработчик рисует геометрические фигуры, текст, изображения и пути с помощью различных методов draw*. По данным Android Developers Documentation (2025), Canvas является основным инструментом для кастомной отрисовки в Android и используется вместе с классом Paint для задания стилей и цветов. Каждый вызов onDraw() получает готовый экземпляр Canvas, связанный с bitmap-буфером вью.
Главное
Canvas — это центральный класс 2D-графики в Android, который предоставляет методы для рисования на плоскости. Каждый Canvas привязан к Bitmap — области памяти, в которую записываются результаты операций рисования. Когда View перерисовывается, система передаёт в onDraw() Canvas, уже связанный с буфером этого View.
Canvas работает в системе координат, где (0,0) — верхний левый угол, ось X идёт вправо, ось Y — вниз. Все операции рисования используют текущее состояние Canvas, включая матрицу трансформации, область отсечения (clip) и стиль Paint. По данным Android Documentation (2025), Canvas API включает более 30 методов draw* для разных типов графических примитивов.
С Android 3.0 (API 11) Canvas поддерживает аппаратное ускорение через DisplayList. В ускоренном режиме Canvas-операции записываются в DisplayList и затем исполняются на GPU. Это даёт значительный прирост производительности, но накладывает ограничения: некоторые Canvas-операции (clipPath с не-Path-клипом) не поддерживаются в ускоренном режиме.
Canvas предоставляет набор методов для рисования базовых примитивов. Каждый метод принимает геометрические параметры и объект Paint, определяющий визуальный стиль.
| Метод | Назначение | Пример |
|---|---|---|
| drawRect() | Рисование прямоугольника | canvas.drawRect(10f, 10f, 100f, 100f, paint) |
| drawCircle() | Рисование круга | canvas.drawCircle(50f, 50f, 30f, paint) |
| drawLine() | Рисование линии | canvas.drawLine(0f, 0f, 100f, 100f, paint) |
| drawText() | Рисование текста | canvas.drawText("Hello", 10f, 20f, paint) |
| drawBitmap() | Рисование изображения | canvas.drawBitmap(bitmap, 0f, 0f, paint) |
| drawPath() | Рисование произвольного пути | canvas.drawPath(path, paint) |
| drawOval() | Рисование овала | canvas.drawOval(rectF, paint) |
| drawArc() | Рисование дуги или сектора | canvas.drawArc(rectF, 0f, 90f, true, paint) |
Каждый метод может быть вызван в любой последовательности — Canvas рисует в порядке вызовов, накладывая новые элементы поверх предыдущих. Для сложных композиций используется техника многослойной отрисовки с сохранением и восстановлением состояния Canvas.
Paint — это класс, который определяет, как Canvas будет рисовать. Без Paint методы Canvas бесполезны — он задаёт цвет, толщину линии, стиль заливки, размер текста, эффекты (тень, градиент, размытие) и режим наложения.
Paint имеет ключевые свойства: color (цвет), style (FILL, STROKE или FILL_AND_STROKE), strokeWidth (толщина линии), textSize (размер текста), isAntiAlias (сглаживание), shader (градиент или изображение). Создание объекта Paint — дорогая операция, поэтому его следует создавать один раз и переиспользовать.
private val fillPaint = Paint().apply {
color = Color.parseColor("#FF6200EE")
style = Paint.Style.FILL
isAntiAlias = true
}
private val strokePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 4f
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(100f, 100f, 80f, fillPaint)
canvas.drawCircle(100f, 100f, 80f, strokePaint)
}
В этом примере используются два Paint: один для заливки круга фиолетовым цветом, второй — для белой обводки. Разделение на fill и stroke — стандартная практика, позволяющая гибко управлять внешним видом без пересоздания объектов.
Для градиентных заливок Paint принимает Shader — подклассы LinearGradient, RadialGradient, SweepGradient. Shader привязывается к Paint через метод setShader() и определяет, как цвет меняется по области отрисовки.
Canvas поддерживает матричные трансформации системы координат: translate() (сдвиг), rotate() (поворот), scale() (масштабирование) и skew() (наклон). Трансформации накапливаются: каждый последующий вызов применяется поверх предыдущих.
Управление стеком трансформаций осуществляется через save() и restore(). save() сохраняет текущее состояние Canvas (матрицу, clip) в стеке, restore() восстанавливает последнее сохранённое состояние. Между save() и restore() могут быть вложенные трансформации.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save() // Save initial state
canvas.translate(100f, 100f) // Translate origin
// First square
canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)
canvas.save() // Save after translate
canvas.rotate(45f, 25f, 25f) // Rotate around square center
// Rotated square
canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
canvas.restore() // Restore after rotate
canvas.restore() // Restore initial state
}
Всегда используйте save() перед началом трансформаций и restore() после их завершения. Несбалансированные save/restore приводят к накоплению матрицы и некорректной отрисовке последующих элементов.
Для сложной графики Canvas комбинируется с Bitmap и Path. Bitmap используется для загрузки изображений и кэширования отрендеренных кадров. Path — для построения произвольных кривых Безье, многоугольников и сложных контуров.
Техника offscreen-рендеринга: создаётся Bitmap, на него рисуется сложная сцена через отдельный Canvas, и затем готовый Bitmap выводится в onDraw() через drawBitmap(). Это позволяет обновлять сложную графику без полной перерисовки на каждый кадр.
private var offscreenBitmap: Bitmap? = null
private var offscreenCanvas: Canvas? = null
fun buildOffscreenScene(width: Int, height: Int) {
offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
offscreenCanvas = Canvas(offscreenBitmap)
// Render scene once
offscreenCanvas!!.drawColor(Color.WHITE)
drawGrid(offscreenCanvas!!)
drawComplexShape(offscreenCanvas!!)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
offscreenBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}
Path используется для построения сложных контуров: кривые Безье (quadTo, cubicTo), дуги (arcTo), линии (lineTo). Path может быть замкнутым (close()) для залитых фигур или открытым для линий. Основные методы drawPath() объединяют Path и Paint для конечной отрисовки.
Производительность Canvas-отрисовки напрямую влияет на FPS приложения. Основные проблемы: избыточные аллокации в onDraw(), частые invalidate() и неоптимальное использование Hardware Acceleration.
Paint, Path, Rect, RectF и другие объекты должны создаваться однократно в инициализаторе или методе setup(). Каждая аллокация в onDraw() создаёт нагрузку на сборщик мусора, что приводит к микро-лагам. Исключение — static или companion object для immutable-объектов.
Если большая часть View скрыта за пределами видимой области (например, в ScrollView), используйте canvas.clipRect() для ограничения отрисовки. Hardware Acceleration в этом случае дополнительно оптимизирует отсечение невидимых пикселей.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Clip to visible area
canvas.save()
canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)
// Render large scene
renderScene(canvas)
canvas.restore()
}
Используйте View.setLayerType() с LAYER_TYPE_HARDWARE для View со сложной Canvas-графикой, которая обновляется редко. Для часто обновляемой анимации, наоборот, используйте LAYER_TYPE_NONE, чтобы избежать дорогого обновления аппаратного слоя. Профилируйте через GPU Profile Rendering и Android Studio Profiler.
Часто задаваемые вопросы
Да, можно создать Canvas(Bitmap) — конструктор, принимающий Bitmap. Это используется для offscreen-рендеринга: вы рисуете на Bitmap, а затем выводите его через drawBitmap(). Также Canvas может быть создан из Surface (для SurfaceView) или через Picture.
Canvas — это API для рисования на View в UI-потоке. SurfaceView — это отдельный Surface, который может обновляться из фонового потока через собственный Canvas (lockCanvas() / unlockCanvasAndPost()). SurfaceView предпочтителен для игр и видео.
Самая частая причина — несбалансированные save() и restore(). Если после трансформации (translate, rotate) не вызвать restore(), Canvas остаётся в изменённой системе координат, и последующие элементы рисуются со смещением. Проверьте пары save()/restore().
Используйте Paint.setTextAlign(Paint.Align.CENTER) для горизонтального центрирования и Paint.getTextBounds() или Paint.descent() / ascent() для вертикального. Центрирование: canvas.drawText(text, centerX, centerY - (ascent + descent) / 2, paint).
Canvas сам по себе не анимирует — он только рисует текущее состояние. Анимация достигается через последовательные вызовы invalidate() (или postInvalidateOnAnimation()), каждый раз рисуя новый кадр в onDraw(). Для плавных анимаций используйте ValueAnimator или Choreographer для синхронизации с Vsync.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также