Le viste personalizzate sono la base dell'interfaccia unica di qualsiasi app mobile. Le viste personalizzate nelle app mobili permettono di creare grafica, animazioni ed elementi propri non disponibili nei componenti standard. Secondo Apple Developer, 2025, drawRect rimane il metodo principale di disegno personalizzato nello sviluppo mobile iOS. Su Android, onDraw con Canvas svolge lo stesso ruolo, e in Flutter — CustomPainter.
Punti chiave
Le viste personalizzate sono componenti d'interfaccia definiti dall'utente che sovrascrivono i metodi standard di disegno e misura per creare grafica unica. Grafici, animazioni, diagrammi, elementi chat personalizzati e maschere AR — questi sono scenari tipici di utilizzo delle viste personalizzate nelle app mobili. I componenti UI standard (Button, TextView, Label) coprono l'80% delle attività, ma il restante 20% richiede il controllo completo sul disegno nello sviluppo mobile. Il disegno nello sviluppo mobile è un'abilità che distingue uno sviluppatore junior da un senior.
Le viste personalizzate nelle app mobili sono necessarie per grafica 2D unica, animazioni complesse, grafici e visualizzazione dei dati. Se l'UIKit standard non fornisce l'elemento necessario — viene creata una vista personalizzata. Nelle app mobili, le viste personalizzate vengono utilizzate per barre di progresso, pulsanti personalizzati, riempimenti sfumati, icone animate e canvas per disegnare con il dito. È importante non abusarne — per stili semplici basta configurare i componenti standard tramite l'API di aspetto.
Su iOS, le viste personalizzate vengono create ereditando da UIView e sovrascrivendo il metodo drawRect. drawRect viene chiamato al primo render e dopo ogni chiamata a setNeedsDisplay(). All'interno di drawRect, lo sviluppatore ottiene un CGContext tramite UIGraphicsGetCurrentContext() — una tela per linee, forme, gradienti e testo. Core Graphics (Quartz 2D) è un motore di rendering di basso livello su CPU che offre il controllo completo su ogni pixel. UIBezierPath è un wrapper conveniente per creare percorsi con sintassi concisa.
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 fornisce CGContext — una tela per tutte le operazioni di disegno. CGGradient crea gradienti lineari e radiali con supporto di CGColorSpace (sRGB, Display P3). Le trasformazioni vengono eseguite tramite CGAffineTransform — una matrice 3x3 per rotazione, ridimensionamento e traslazione. CGContextTranslateCTM, CGContextRotateCTM e CGContextScaleCTM modificano il sistema di coordinate. Core Graphics funziona su CPU, quindi la grafica complessa può caricare il processore — utilizzare Metal per l'accelerazione GPU.
UIBezierPath è un wrapper orientato agli oggetti su CGPath. Il metodo addArc(withCenter:radius:startAngle:endAngle:clockwise:) crea archi e cerchi, addCurve crea curve di Bézier. I percorsi supportano operazioni di unione, intersezione e sottrazione tramite addClip. UIBezierPath è conveniente per maschere personalizzate (maskView), percorsi di ritaglio e forme complesse — stelle, cuori, icone personalizzate.
Su Android, una vista personalizzata estende View con onDraw(Canvas) sovrascritto. Canvas contiene tutti i metodi di disegno nello sviluppo mobile: drawCircle, drawRect, drawLine, drawText, drawBitmap, drawPath e drawRoundRect. Paint configura i parametri — colore, spessore della linea, stile (FILL, STROKE, FILL_AND_STROKE), anti-alias (isAntiAlias) e dimensione del testo. onDraw viene chiamato ad ogni invalidate() e non deve contenere operazioni pesanti — tutti i calcoli vengono memorizzati nella 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 su Android è l'equivalente di UIBezierPath: addCircle, addRect, addOval, lineTo, cubicTo e quadTo. PathMeasure calcola la lunghezza del percorso e le coordinate in qualsiasi punto — utile per animare il movimento di un oggetto lungo un percorso. Per le animazioni, si utilizza ValueAnimator con invalidate() nel callback. L'animatore può cambiare colore, raggio, posizione — qualsiasi proprietà che influenzi il disegno. Le viste personalizzate su Android supportano anche StateListDrawable e animated-vector per animazioni tramite XML.
I gradienti su Android vengono impostati tramite Shader — LinearGradient, RadialGradient e SweepGradient. Shader viene assegnato a Paint tramite paint.setShader(shader). LinearGradient genera un gradiente lineare tra due punti con posizioni di colore opzionali. RadialGradient — radiale dal centro ai bordi. SweepGradient — conico attorno a un punto centrale. Shader.TileMode (CLAMP, REPEAT, MIRROR) definisce il comportamento oltre i limiti dell'area.
Flutter utilizza CustomPainter — una classe con i metodi paint(Canvas canvas, Size size) e shouldRepaint. Canvas in Flutter fornisce gli stessi metodi che in Android: drawCircle, drawRect, drawLine, drawPath, drawParagraph e drawImage. Paint in Flutter configura colore, spessore, stile e anti-alias. Flutter non usa drawRect né onDraw — tutto il disegno avviene tramite CustomPainter, incorporato nel widget 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 determina se CustomPainter deve essere ridisegnato. Restituisci true solo quando cambiano i dati che influenzano il rendering — colore, dimensione, dati del grafico. RepaintBoundary isola il ridisegno in un layer separato — usalo per widget che cambiano raramente. Flutter non ha setNeedsDisplay o invalidate — il ridisegno viene attivato tramite setState o ValueNotifier con ascolto delle modifiche.
Flutter supporta LinearGradient, RadialGradient e SweepGradient — gli stessi tipi che in Android. Gradient crea uno Shader per Paint tramite createShader(Rect bounds). Le trasformazioni vengono eseguite tramite canvas.rotate, canvas.scale, canvas.translate e canvas.skew. Flutter utilizza una matrice di trasformazione 4x4 (Matrix4) per il widget Transform. Le animazioni di disegno in Flutter vengono realizzate con AnimationController in combinazione con setState per ridisegnare CustomPainter.
La corretta misura delle viste personalizzate è la base del comportamento prevedibile dell'interfaccia. Su iOS, la dimensione della Vista viene determinata tramite sizeThatFits e intrinsicContentSize. sizeThatFits restituisce la dimensione ottimale per i vincoli dati. intrinsicContentSize — la dimensione naturale del contenuto senza vincoli esterni. Su Android, la misura viene implementata in onMeasure con parametri MeasureSpec (UNSPECIFIED, EXACTLY, AT_MOST). In Flutter, la dimensione viene passata a paint(Canvas, Size) e può essere calcolata tramite computeDryLayout o LayoutBuilder.
intrinsicContentSize restituisce un CGSize — la dimensione naturale della Vista basata sul suo contenuto. setContentHuggingPriority e setContentCompressionResistancePriority controllano il comportamento durante l'allungamento e la compressione. sizeThatFits viene chiamato dal sistema per calcolare la dimensione in Auto Layout. Per viste personalizzate, sovrascrivi intrinsicContentSize se il tuo componente ha una dimensione naturale — ad esempio, un cerchio dovrebbe essere sempre 100x100.
onMeasure accetta widthMeasureSpec e heightMeasureSpec. MeasureSpec contiene modalità e dimensione: UNSPECIFIED (senza vincoli, la Vista sceglie la propria dimensione), EXACTLY (dimensione esatta dal genitore), AT_MOST (dimensione massima). setMeasuredDimension(width, height) salva il risultato della misura. Quando i dati cambiano, chiama requestLayout() per rimisurare. I ViewGroup annidati richiedono la sovrascrittura di onLayout per disporre gli elementi figli.
| Piattaforma | Metodo di misura | Chiamata al cambiamento | Ritorno |
|---|---|---|---|
| iOS | sizeThatFits / intrinsicContentSize | setNeedsLayout / invalidateIntrinsicContentSize | CGSize |
| Android | onMeasure / setMeasuredDimension | requestLayout / invalidate | void (salva dimensione) |
| Flutter | computeDryLayout | setState / LayoutBuilder | Size |
computeDryLayout è un metodo RenderObject per calcolare la dimensione senza un ciclo di layout completo. LayoutBuilder.getSize — un'alternativa per ottenere lo spazio disponibile nel metodo build. In Flutter, la dimensione della tela viene passata a paint come Size e non richiede misura separata — CustomPainter disegna entro i limiti passati. Tuttavia, per RenderObject personalizzati, è necessario sovrascrivere performLayout e computeDryLayout.
Domande frequenti
drawRect è un metodo UIView per il disegno personalizzato tramite Core Graphics. Viene chiamato al primo render della Vista e dopo ogni chiamata a setNeedsDisplay(). Usa CGContext all'interno di drawRect per linee, forme e testo.
Crea una classe che estende View, sovrascrivi onDraw(Canvas canvas) e usa l'API Canvas: drawCircle, drawRect, drawPath. Paint configura colore, spessore e stile. Per aggiornare, chiama invalidate().
CustomPainter è una classe Flutter per il disegno personalizzato su Canvas. Implementa i metodi paint(Canvas, Size) e shouldRepaint. Viene posizionato tramite il widget CustomPaint. Flutter non ha drawRect — tutto il disegno avviene tramite CustomPainter.
Su iOS usa sizeThatFits e intrinsicContentSize. Su Android — onMeasure con MeasureSpec. In Flutter, la dimensione viene passata a paint(Canvas, Size) o calcolata tramite computeDryLayout. Ogni piattaforma usa il proprio metodo di misura.
Le viste personalizzate sono necessarie per grafica unica, animazioni, grafici ed elementi che non possono essere implementati con UIKit o View standard. Per stili semplici, basta configurare i componenti standard — non complicare l'architettura inutilmente.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.