Кастомные 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() в колбэке. Animator может менять цвет, радиус, положение — любое свойство, влияющее на рисование. Кастомные 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 года. Мы проконсультируем вас и предложим наилучшее решение.