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, който вече е свързан с буфера на този изглед.
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() // Запази началното състояние
canvas.translate(100f, 100f) // Премести началото
// Първи квадрат
canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)
canvas.save() // Запази след преместване
canvas.rotate(45f, 25f, 25f) // Завърти около центъра на квадрата
// Завъртян квадрат
canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
canvas.restore() // Възстанови след завъртане
canvas.restore() // Възстанови началното състояние
}
Винаги използвайте 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)
// Рендерирай сцената веднъж
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() натоварва garbage collector, което води до микро-забавяния. Изключение — static или companion object за неизменяеми обекти.
Ако голяма част от View е скрита извън видимата област (например в ScrollView), използвайте canvas.clipRect() за ограничаване на рисуването. Хардуерното ускорение в този случай допълнително оптимизира изрязването на невидими пиксели.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Изрежи до видимата област
canvas.save()
canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)
// Рендерирай голяма сцена
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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също