Кастомні View — основа унікального інтерфейсу будь-якого мобільного додатка. Кастомні View в мобільних додатках дозволяють створювати власну графіку, анімації та елементи, недоступні в стандартних компонентах. За даними Apple Developer, 2025, drawRect залишається основним методом кастомного малювання в мобільній розробці на iOS. На Android ту ж роль виконує onDraw з Canvas, а у Flutter — CustomPainter.
Головне
Кастомні View — це користувацькі компоненти інтерфейсу, які перевизначають стандартні методи відмальовування та вимірювання для створення унікальної графіки. Графіки, анімації, діаграми, кастомні елементи чату та AR-маски — типові сценарії застосування кастомних View у мобільних додатках. Стандартні UI-компоненти (Button, TextView, Label) покривають 80% завдань, але решта 20% потребують повного контролю над малюванням у мобільній розробці. Малювання в мобільній розробці — навичка, яка відрізняє junior-розробника від senior.
Кастомні View у мобільних додатках потрібні для унікальної 2D-графіки, складних анімацій, графіків та візуалізації даних. Якщо стандартний UIKit не надає потрібного елемента — створюється кастомний View. У мобільних додатках кастомні View використовуються для прогрес-барів, кастомних кнопок, градієнтних заливок, анімованих іконок та канвасів для малювання пальцем. Важно не зловживати — для простих стилів достатньо налаштувати стандартні компоненти через Appearance API.
На iOS кастомні View створюються через наслідування від UIView та перевизначення методу drawRect. drawRect викликається при першому відмальовуванні та після кожного виклику setNeedsDisplay(). Всередині drawRect розробник отримує CGContext через UIGraphicsGetCurrentContext() — полотно для ліній, фігур, градієнтів та тексту. Core Graphics (Quartz 2D) — низькорівневий двигун рендерингу на CPU, який дає повний контроль над кожним пікселем. UIBezierPath — зручна обгортка для створення шляхів з лаконічним синтаксисом.
class CircleView: UIView {
private var fillColor: UIColor = .systemBlue
override func drawRect(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.setFillColor(fillColor.cgColor)
ctx.addEllipse(in: bounds.insetBy(dx: 4, dy: 4))
ctx.drawPath(using: .fill)
}
func updateColor(_ newColor: UIColor) {
fillColor = newColor
setNeedsDisplay()
}
}
Core Graphics надає CGContext — полотно для всіх операцій малювання. CGGradient створює лінійні та радіальні градієнти з підтримкою CGColorSpace (sRGB, Display P3). Трансформації виконуються через CGAffineTransform — матриця 3x3 для повороту, масштабування та зміщення. CGContextTranslateCTM, CGContextRotateCTM та CGContextScaleCTM змінюють систему координат. Core Graphics працює на CPU, тому складна графіка може навантажувати процесор — для GPU-прискорення використовуйте Metal.
UIBezierPath — об'єктно-орієнтована обгортка над CGPath. Метод addArc(withCenter:radius:startAngle:endAngle:clockwise:) створює дуги та кола, addCurve — криві Безьє. Шляхи підтримують операції union, intersection та subtraction через addClip. UIBezierPath зручний для кастомних масок (maskView), обрізних контурів та складних фігур — зірки, сердечка, кастомні іконки.
На Android кастомний View успадковується від View з перевизначенням onDraw(Canvas). Canvas містить всі методи малювання в мобільній розробці: drawCircle, drawRect, drawLine, drawText, drawBitmap, drawPath та drawRoundRect. Paint налаштовує параметри — колір, товщину лінії, стиль (FILL, STROKE, FILL_AND_STROKE), згладжування (isAntiAlias) та розмір тексту. onDraw викликається кожного разу при invalidate() і не повинен містити важких операцій — всі обчислення кешуються.
class CustomCircleView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private val paint = Paint().apply {
style = Paint.Style.FILL
color = Color.BLUE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 4f
canvas.drawCircle(cx, cy, radius, paint)
}
}
Path на Android — аналог UIBezierPath: addCircle, addRect, addOval, lineTo, cubicTo та quadTo. PathMeasure обчислює довжину шляху та координати в довільній точці — корисно для анімації руху об'єкта вздовж шляху. Для анімацій використовується ValueAnimator з invalidate() у колбеку. Аніматор може змінювати колір, радіус, положення — будь-яку властивість, що впливає на малювання. Кастомні View на Android також підтримують StateListDrawable та animated-vector для анімації через XML.
Градієнти на Android задаються через Shader — LinearGradient, RadialGradient та SweepGradient. Shader присвоюється Paint через paint.setShader(shader). LinearGradient генерує лінійний градієнт між двома точками з опціональними позиціями кольорів. RadialGradient — радіальний від центру до країв. SweepGradient — конічний навколо центральної точки. Shader.TileMode (CLAMP, REPEAT, MIRROR) визначає поведінку за межами області.
Flutter використовує CustomPainter — клас з методами paint(Canvas canvas, Size size) та shouldRepaint. Canvas у Flutter надає такі ж методи, як в Android: drawCircle, drawRect, drawLine, drawPath, drawParagraph та drawImage. Paint у Flutter налаштовує колір, товщину, стиль та згладжування. Flutter не використовує drawRect або onDraw — все малювання йде через CustomPainter, вбудований у CustomPaint віджет.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill
..isAntiAlias = true;
final radius = size.shortestSide / 2 - 4;
canvas.drawCircle(size.center(Offset.zero), radius, paint);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
shouldRepaint визначає, чи потрібно перемальовувати CustomPainter. Повертайте true тільки при зміні даних, що впливають на відмальовування — колір, розмір, дані графіка. RepaintBoundary ізолює перемальовування в окремий шар — використовуйте для віджетів, які рідко змінюються. У Flutter немає setNeedsDisplay або invalidate — перемальовування запускається через setState або ValueNotifier з прослуховуванням змін.
Flutter підтримує LinearGradient, RadialGradient та SweepGradient — ті ж типи, що в Android. Gradient створює Shader для Paint через createShader(Rect bounds). Трансформації виконуються через canvas.rotate, canvas.scale, canvas.translate та canvas.skew. Flutter використовує матрицю перетворень 4x4 (Matrix4) для Transform віджета. Анімації малювання у Flutter робляться через AnimationController у зв'язці з setState для перемальовування CustomPainter.
Коректне вимірювання кастомних View — основа передбачуваної поведінки інтерфейсу. На iOS розмір View визначається через sizeThatFits та intrinsicContentSize. sizeThatFits повертає оптимальний розмір для заданих обмежень. intrinsicContentSize — природний розмір контенту без зовнішніх обмежень. На Android вимірювання реалізується в onMeasure з параметрами MeasureSpec (UNSPECIFIED, EXACTLY, AT_MOST). У Flutter розмір передається в paint(Canvas, Size) і може бути обчислений через computeDryLayout або LayoutBuilder.
intrinsicContentSize повертає CGSize — природній розмір View на основі його контенту. setContentHuggingPriority та setContentCompressionResistancePriority керують поведінкою при розтягуванні та стисненні. sizeThatFits викликається системою для обчислення розміру в Auto Layout. Для кастомних View перевизначте intrinsicContentSize, якщо ваш компонент має природній розмір — наприклад, коло завжди повинно бути 100x100.
onMeasure приймає widthMeasureSpec та heightMeasureSpec. MeasureSpec містить режим та розмір: UNSPECIFIED (без обмежень, View сам вибирає розмір), EXACTLY (точний розмір від батька), AT_MOST (максимальний розмір). setMeasuredDimension(width, height) зберігає результат вимірювання. При зміні даних викликайте requestLayout() для повторного вимірювання. Вкладені ViewGroup потребують перевизначення onLayout для розстановки дочірніх елементів.
| Платформа | Метод вимірювання | Виклик при зміні | Повернення |
|---|---|---|---|
| iOS | sizeThatFits / intrinsicContentSize | setNeedsLayout / invalidateIntrinsicContentSize | CGSize |
| Android | onMeasure / setMeasuredDimension | requestLayout / invalidate | void (зберігає розмір) |
| Flutter | computeDryLayout | setState / LayoutBuilder | Size |
computeDryLayout — метод RenderObject для обчислення розміру без повного циклу компонування. LayoutBuilder.getSize — альтернатива для отримання доступного простору в build-методі. У Flutter розмір полотна передається в paint як Size і не вимагає окремого вимірювання — CustomPainter малює всередині переданих меж. Однак для кастомних RenderObject треба перевизначати performLayout та computeDryLayout.
Часті запитання
drawRect — метод UIView для кастомного малювання через Core Graphics. Викликається при першому відмальовуванні View та після кожного виклику setNeedsDisplay(). Використовуйте CGContext всередині drawRect для ліній, фігур та тексту.
Створіть клас, що успадковує View, перевизначте onDraw(Canvas canvas) та використовуйте Canvas API: drawCircle, drawRect, drawPath. Paint налаштовує колір, товщину та стиль. Для оновлення викликайте invalidate().
CustomPainter — клас Flutter для кастомного малювання на Canvas. Реалізуйте методи paint(Canvas, Size) та shouldRepaint. Розміщується через віджет CustomPaint. У Flutter немає drawRect — все малювання йде через CustomPainter.
На iOS використовуйте sizeThatFits та intrinsicContentSize. На Android — onMeasure з MeasureSpec. У Flutter розмір передається в paint(Canvas, Size) або обчислюється через computeDryLayout. Кожна платформа використовує свій метод вимірювання.
Кастомні View потрібні для унікальної графіки, анімацій, графіків та елементів, які неможливо реалізувати стандартними UIKit або View. Для простих стилів достатньо налаштувати стандартні компоненти — не ускладнюйте архітектуру без необхідності.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.