Canvas в Android — це клас з пакета android.graphics, який надає API для 2D-рендерингу на View. Canvas працює як полотно, на якому розробник малює геометричні фігури, текст, зображення та шляхи за допомогою різних методів draw*. За даними Android Developers Documentation (2025), Canvas є основним інструментом для кастомного рендерингу в Android і використовується разом з класом Paint для завдання стилів і кольорів. Кожен виклик onDraw() отримує готовий екземпляр Canvas, пов’язаний з bitmap-буфером вьe.
Головне
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() та неоптимальне використання апаратного прискорення.
Paint, Path, Rect, RectF та інші об—єкти повинні створюватися одноразово в ініціалізаторі або методі setup(). Кожна алокація в onDraw() створює навантаження на збирач сміття, що призводить до мікролагів. Виняток — static або companion object для immutable-об—єктів.
Якщо велика частина View прихована за межами видимоє області (наприклад, у ScrollView), використовуйте canvas.clipRect() для обмеження рендерингу. Апаратне прискорення в цьому випадку додатково оптимізує відсікання невидимих пікселів.
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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також