Vizualizările personalizate sunt baza interfeței unice a oricărei aplicații mobile. Vizualizările personalizate în aplicațiile mobile vă permit să creați propria grafică, animații și elemente indisponibile în componentele standard. Potrivit Apple Developer, 2025, drawRect rămâne metoda principală de desenare personalizată în dezvoltarea mobilă iOS. Pe Android, onDraw cu Canvas joacă același rol, iar în Flutter — CustomPainter.
Puncte cheie
Vizualizările personalizate sunt componente de interfață definite de utilizator care suprascriu metodele standard de desenare și măsurare pentru a crea grafică unică. Grafice, animații, diagrame, elemente de chat personalizate și măști AR — acestea sunt scenarii tipice de utilizare a vizualizărilor personalizate în aplicațiile mobile. Componentele UI standard (Button, TextView, Label) acoperă 80% din sarcini, dar restul de 20% necesită control total asupra desenării în dezvoltarea mobilă. Desenarea în dezvoltarea mobilă este o abilitate care diferențiază un dezvoltator junior de un senior.
Vizualizările personalizate în aplicațiile mobile sunt necesare pentru grafică 2D unică, animații complexe, diagrame și vizualizare a datelor. Dacă UIKit standard nu oferă elementul necesar — se creează o vizualizare personalizată. În aplicațiile mobile, vizualizările personalizate sunt utilizate pentru bare de progres, butoane personalizate, umpleri gradient, pictograme animate și pânze pentru desenare cu degetul. Este important să nu abuzați — pentru stiluri simple, este suficient să configurați componentele standard prin API-ul de aspect.
Pe iOS, vizualizările personalizate sunt create prin moștenirea UIView și suprascrierea metodei drawRect. drawRect este apelat la primul randament și după fiecare apel setNeedsDisplay(). În interiorul drawRect, dezvoltatorul obține un CGContext prin UIGraphicsGetCurrentContext() — o pânză pentru linii, forme, gradiente și text. Core Graphics (Quartz 2D) este un motor de randare de nivel scăzut pe CPU care oferă control total asupra fiecărui pixel. UIBezierPath este un înveliș convenabil pentru crearea de căi cu sintaxă concisă.
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 oferă CGContext — o pânză pentru toate operațiile de desenare. CGGradient creează gradiente liniare și radiale cu suport pentru CGColorSpace (sRGB, Display P3). Transformările sunt efectuate prin CGAffineTransform — o matrice 3x3 pentru rotire, scalare și translație. CGContextTranslateCTM, CGContextRotateCTM și CGContextScaleCTM modifică sistemul de coordonate. Core Graphics rulează pe CPU, astfel încât grafica complexă poate încărca procesorul — utilizați Metal pentru accelerare GPU.
UIBezierPath este un înveliș orientat pe obiecte peste CGPath. Metoda addArc(withCenter:radius:startAngle:endAngle:clockwise:) creează arcuri și cercuri, addCurve creează curbe Bézier. Căile suportă operații de uniune, intersectare și scădere prin addClip. UIBezierPath este convenabil pentru măști personalizate (maskView), căi de decupare și forme complexe — stele, inimi, pictograme personalizate.
Pe Android, o vizualizare personalizată extinde View cu onDraw(Canvas) suprascris. Canvas conține toate metodele de desenare în dezvoltarea mobilă: drawCircle, drawRect, drawLine, drawText, drawBitmap, drawPath și drawRoundRect. Paint configurează parametrii — culoare, grosimea liniei, stil (FILL, STROKE, FILL_AND_STROKE), anti-aliasing (isAntiAlias) și dimensiunea textului. onDraw este apelat la fiecare invalidate() și nu trebuie să conțină operații grele — toate calculele sunt stocate în cache.
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 pe Android este echivalentul lui UIBezierPath: addCircle, addRect, addOval, lineTo, cubicTo și quadTo. PathMeasure calculează lungimea căii și coordonatele în orice punct — util pentru animarea mișcării unui obiect de-a lungul unei căi. Pentru animații, se utilizează ValueAnimator cu invalidate() în callback. Animatorul poate schimba culoarea, raza, poziția — orice proprietate care afectează desenarea. Vizualizările personalizate pe Android suportă, de asemenea, StateListDrawable și animated-vector pentru animație prin XML.
Gradienturile pe Android se setează prin Shader — LinearGradient, RadialGradient și SweepGradient. Shader se atribuie lui Paint prin paint.setShader(shader). LinearGradient generează un gradient liniar între două puncte cu poziții de culoare opționale. RadialGradient — radial de la centru la margini. SweepGradient — conic în jurul unui punct central. Shader.TileMode (CLAMP, REPEAT, MIRROR) definește comportamentul dincolo de limitele zonei.
Flutter utilizează CustomPainter — o clasă cu metodele paint(Canvas canvas, Size size) și shouldRepaint. Canvas în Flutter oferă aceleași metode ca în Android: drawCircle, drawRect, drawLine, drawPath, drawParagraph și drawImage. Paint în Flutter configurează culoarea, grosimea, stilul și anti-aliasing-ul. Flutter nu folosește drawRect sau onDraw — toată desenarea se face prin CustomPainter, încorporat în widgetul 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 determină dacă CustomPainter trebuie redesenat. Returnați true numai când se modifică datele care afectează randarea — culoare, dimensiune, datele graficului. RepaintBoundary izolează redesenearea într-un strat separat — utilizați pentru widgeturi care se schimbă rar. Flutter nu are setNeedsDisplay sau invalidate — redesenearea este declanșată prin setState sau ValueNotifier cu ascultarea modificărilor.
Flutter suportă LinearGradient, RadialGradient și SweepGradient — aceleași tipuri ca în Android. Gradient creează un Shader pentru Paint prin createShader(Rect bounds). Transformările sunt efectuate prin canvas.rotate, canvas.scale, canvas.translate și canvas.skew. Flutter utilizează o matrice de transformare 4x4 (Matrix4) pentru widgetul Transform. Animațiile de desenare în Flutter se fac cu AnimationController împreună cu setState pentru redesenearea CustomPainter.
Măsurarea corectă a vizualizărilor personalizate este baza comportamentului previzibil al interfeței. Pe iOS, dimensiunea Vizualizării se determină prin sizeThatFits și intrinsicContentSize. sizeThatFits returnează dimensiunea optimă pentru constrângerile date. intrinsicContentSize — dimensiunea naturală a conținutului fără constrângeri externe. Pe Android, măsurarea este implementată în onMeasure cu parametrii MeasureSpec (UNSPECIFIED, EXACTLY, AT_MOST). În Flutter, dimensiunea este transmisă lui paint(Canvas, Size) și poate fi calculată prin computeDryLayout sau LayoutBuilder.
intrinsicContentSize returnează un CGSize — dimensiunea naturală a Vizualizării pe baza conținutului său. setContentHuggingPriority și setContentCompressionResistancePriority controlează comportamentul în timpul întinderii și comprimării. sizeThatFits este apelat de sistem pentru a calcula dimensiunea în Auto Layout. Pentru vizualizări personalizate, suprascrieți intrinsicContentSize dacă componenta dvs. are o dimensiune naturală — de exemplu, un cerc ar trebui să fie întotdeauna 100x100.
onMeasure acceptă widthMeasureSpec și heightMeasureSpec. MeasureSpec conține mod și dimensiune: UNSPECIFIED (fără constrângeri, Vizualizarea își alege dimensiunea), EXACTLY (dimensiune exactă de la părinte), AT_MOST (dimensiune maximă). setMeasuredDimension(width, height) salvează rezultatul măsurării. La schimbarea datelor, apelați requestLayout() pentru remăsurare. ViewGroup-urile imbricate necesită suprascrierea onLayout pentru aranjarea elementelor copil.
| Platformă | Metodă de măsurare | Apel la schimbare | Returnare |
|---|---|---|---|
| iOS | sizeThatFits / intrinsicContentSize | setNeedsLayout / invalidateIntrinsicContentSize | CGSize |
| Android | onMeasure / setMeasuredDimension | requestLayout / invalidate | void (salvează dimensiunea) |
| Flutter | computeDryLayout | setState / LayoutBuilder | Size |
computeDryLayout este o metodă RenderObject pentru calcularea dimensiunii fără un ciclu complet de layout. LayoutBuilder.getSize — o alternativă pentru obținerea spațiului disponibil în metoda build. În Flutter, dimensiunea pânzei este transmisă lui paint ca Size și nu necesită măsurare separată — CustomPainter desenează în limitele transmise. Cu toate acestea, pentru RenderObject-uri personalizate, trebuie să suprascrieți performLayout și computeDryLayout.
Întrebări frecvente
drawRect este o metodă UIView pentru desenare personalizată prin Core Graphics. Este apelată la primul randament al Vizualizării și după fiecare apel setNeedsDisplay(). Utilizați CGContext în interiorul drawRect pentru linii, forme și text.
Creați o clasă care extinde View, suprascrieți onDraw(Canvas canvas) și utilizați API-ul Canvas: drawCircle, drawRect, drawPath. Paint configurează culoarea, grosimea și stilul. Pentru actualizare, apelați invalidate().
CustomPainter este o clasă Flutter pentru desenare personalizată pe Canvas. Implementați metodele paint(Canvas, Size) și shouldRepaint. Este plasat prin widgetul CustomPaint. Flutter nu are drawRect — toată desenarea se face prin CustomPainter.
Pe iOS utilizați sizeThatFits și intrinsicContentSize. Pe Android — onMeasure cu MeasureSpec. În Flutter, dimensiunea este transmisă lui paint(Canvas, Size) sau calculată prin computeDryLayout. Fiecare platformă folosește propria metodă de măsurare.
Vizualizările personalizate sunt necesare pentru grafică unică, animații, diagrame și elemente care nu pot fi implementate cu UIKit sau View standard. Pentru stiluri simple, este suficient să configurați componentele standard — nu complicați arhitectura inutil.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.