Custom View in Android — essenza, disegno e gestione eventi

Autore: IT Sectr Pubblicato: 2026-07-20 Tempo di lettura: 9 min

Custom View è un componente di interfaccia personalizzato in Android, creato ereditando dalla classe View o dalle sue sottoclassi (Button, TextView, ImageView) e sovrascrivendo i metodi chiave: onDraw per il disegno, onMeasure per misurare le dimensioni e onTouchEvent per gestire i tocchi. Secondo la Guida per Sviluppatori Android di Google (2024), Custom View viene utilizzato quando i componenti standard dell’SDK Android non forniscono il comportamento o l’aspetto richiesti — ad esempio, per animazioni personalizzate, forme non standard, grafici specializzati ed elementi di gioco.

Punti chiave

  • Custom View — creato ereditando da View o dalle sue sottoclassi con sovrascrittura di onDraw, onMeasure, onTouchEvent.
  • Il disegno viene eseguito tramite Canvas API — forme geometriche, testo, percorsi, Bitmap e animazione.
  • onMeasure gestisce le dimensioni corrette della View considerando i parametri di layout e il padding.
  • Gli attributi XML personalizzati vengono dichiarati in res/values/attrs.xml e letti nel costruttore della View.
  • La gestione dei tocchi tramite onTouchEvent consente di aggiungere supporto per gesti, trascinamento e multitouch.

Cos’è Custom View in Android?

Custom View è una classe che eredita da android.view.View (o da una delle sue sottoclassi) e sovrascrive i metodi di sistema per implementare logica personalizzata di disegno, misurazione e gestione degli eventi. Custom View è un meccanismo fondamentale di Android per creare componenti UI unici.

Android offre due approcci per creare Custom View: ereditare da View (disegno completamente personalizzato tramite onDraw) ed ereditare da una sottoclasse View esistente (ad esempio, sovrascrivere Button o TextView per aggiungere funzionalità mantenendo il comportamento di base).

Costruttore di base di un Custom View deve accettare Context e AttributeSet — ciò consente al sistema di creare la View dal markup XML. Se la View viene utilizzata solo dal codice, un costruttore con Context è sufficiente. Un terzo costruttore con stile è necessario per il supporto dei temi Android.

kotlin
class CustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        strokeWidth = 4f
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(width / 2f,
            height / 2f,
            50f, paint)
    }
}

Custom View minimo è costituito da un costruttore e onDraw. Tuttavia, per funzionare correttamente nel sistema di layout Android, anche onMeasure deve essere sovrascritto — altrimenti la View potrebbe visualizzarsi con dimensioni zero o rispondere in modo errato ai parametri di layout wrap_content.

Secondo Google I/O 2023, i Custom View sono utilizzati nel 65% delle app della top 100 di Google Play. I motivi più comuni per creare un Custom View: animazioni personalizzate (35%), grafici e diagrammi personalizzati (25%), elementi di controllo specializzati (20%) e componenti brandizzati (20%).

onMeasure e onLayout: Gestione delle dimensioni

onMeasure è il metodo in cui la View riporta le sue dimensioni desiderate in base al MeasureSpec ricevuto (vincoli del genitore). Senza una corretta implementazione di onMeasure, la View potrebbe avere altezza zero con wrap_content o occupare l’intera area dello schermo con match_parent.

MeasureSpec è composto da una modalità (EXACTLY, AT_MOST, UNSPECIFIED) e un valore. EXACTLY — il genitore ha impostato una dimensione esatta (match_parent o valore fisso). AT_MOST — il genitore ha impostato un massimo (wrap_content). UNSPECIFIED — senza restrizioni (ScrollView, ListView).

kotlin
override fun onMeasure(
    widthMeasureSpec: Int,
    heightMeasureSpec: Int
) {
    val desiredWidth = paddingLeft + paddingRight
        + DEFAULT_WIDTH
    val desiredHeight = paddingTop + paddingBottom
        + DEFAULT_HEIGHT

    val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
    val modeWidth = MeasureSpec.getMode(widthMeasureSpec)

    val resultWidth = when (modeWidth) {
        MeasureSpec.EXACTLY -> measuredWidth
        MeasureSpec.AT_MOST ->
            desiredWidth.coerceAtMost(measuredWidth)
        else -> desiredWidth
    }

    val resultHeight = resolveSize(desiredHeight,
        heightMeasureSpec)

    setMeasuredDimension(resultWidth, resultHeight)
}

resolveSize è un metodo di utilità di Android che semplifica l’implementazione di onMeasure. Prende la dimensione desiderata e MeasureSpec e restituisce il valore corretto: per EXACTLY — la dimensione esatta, per AT_MOST — il minimo tra desiderata e massima, per UNSPECIFIED — la dimensione desiderata.

onLayout per View (non ViewGroup) di solito non viene sovrascritto — viene chiamato dal genitore per posizionare gli elementi figli. Per ViewGroup, onLayout è obbligatorio — è necessario chiamare layout() per ogni View figlio.

onDraw e Canvas API: Disegno

onDraw è il cuore del Custom View. Questo metodo viene chiamato da Android a ogni ridisegno della View. Viene passato un oggetto Canvas, attraverso il quale viene eseguito tutto il disegno: forme geometriche, testo, percorsi, Bitmap e animazioni.

Canvas API fornisce metodi per tutte le operazioni di base: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Per configurare lo stile, viene utilizzato Paint — un oggetto che definisce colore, spessore, stile di riempimento, carattere, ombre ed effetti.

kotlin
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // Sfondo
    canvas.drawRect(0f, 0f,
        width.toFloat(), height.toFloat(), bgPaint)

    // Testo
    canvas.drawText("Custom View",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

    // Cerchio
    canvas.drawCircle(
        width / 2f,
        height / 2f,
        radius, circlePaint)

    // Percorso (curva di Bezier)
    val path = Path().apply {
        moveTo(0f, height.toFloat())
        quadTo(width / 2f, 0f,
            width.toFloat(), height.toFloat())
    }
    canvas.drawPath(path, pathPaint)
}

Paint è il configuratore di stile di disegno. L’anti-aliasing (Paint.ANTI_ALIAS_FLAG) è obbligatorio per bordi morbidi. Il testo subpixel (SUBPIXEL_TEXT_FLAG) migliora la qualità del testo. Style.FILL, STROKE e FILL_AND_STROKE determinano se la forma verrà riempita, contornata o entrambi.

Importante: non creare oggetti Paint, Path, Rect in onDraw — ciò causa allocazioni nel ciclo di disegno e attiva la garbage collection, riducendo gli FPS. Crea tutti gli oggetti di disegno nel costruttore o nel metodo init. Per l’animazione, usa ValueAnimator o ObjectAnimator con invalidate() per attivare il ridisegno.

Attributi XML personalizzati in attrs.xml

Attributi personalizzati consentono di configurare un Custom View dal markup XML proprio come i standard android:layout_width o android:background. Gli attributi vengono dichiarati nel file res/values/attrs.xml con nome, tipo e valore predefinito opzionale.

Tipi di attributi: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (riferimento a risorsa). Per ogni tipo, Android analizza automaticamente il valore dall’XML e lo passa a TypedArray.

xml
<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="circleColor"
            format="color" />
        <attr name="circleRadius"
            format="dimension" />
        <attr name="labelText"
            format="string" />
        <attr name="showAnimation"
            format="booleano" />
    </declare-styleable>
</resources>

Nel costruttore di Custom View, gli attributi vengono letti tramite context.obtainStyledAttributes, che restituisce un TypedArray. TypedArray fornisce metodi di accesso tipizzati: getColor, getDimension, getString, getBoolean, getInt. Dopo la lettura, è necessario chiamare recycle() su TypedArray per liberare le risorse.

kotlin
init {
    val typedArray = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomView
    )

    circleColor = typedArray.getColor(
        R.styleable.CustomView_circleColor,
        Color.BLUE
    )
    circleRadius = typedArray.getDimension(
        R.styleable.CustomView_circleRadius,
        50f
    )
    labelText = typedArray.getString(
        R.styleable.CustomView_labelText
    ) ?: "Default"

    typedArray.recycle()
}

Utilizzo in XML: aggiungi il namespace app (xmlns:app="http://schemas.android.com/apk/res-auto") e usa attributi personalizzati come app:circleColor="@color/red". Android Studio autocompleterà e validerà i tipi di attributi se la dichiarazione in attrs.xml è corretta.

Gestione dei tocchi con onTouchEvent

onTouchEvent è il metodo chiamato a ogni tocco sulla View. Riceve un oggetto MotionEvent con informazioni sul tipo di evento (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), coordinate, pressione e numero di dita (multitouch).

Per gestire gesti complessi (scorrimento, pizzicamento, pressione lunga), usa GestureDetector o ScaleGestureDetector in combinazione con onTouchEvent. GestureDetector semplifica il riconoscimento di onSingleTapUp, onFling, onLongPress, onDoubleTap e altri gesti standard.

kotlin
private val gestureDetector = GestureDetector(
    context, object : GestureDetector.SimpleOnGestureListener() {
    override fun onSingleTapUp(e: MotionEvent): Boolean {
        handleTap(e.x, e.y)
        return true
    }
})

override fun onTouchEvent(event: MotionEvent): Boolean {
    val handled = gestureDetector.onTouchEvent(event)

    when (event.action) {
        MotionEvent.ACTION_MOVE -> {
            currentX = event.x
            currentY = event.y
            invalidate()
            return true
        }
    }

    return handled || super.onTouchEvent(event)
}

Hit-testing — determinare se un tocco ha raggiunto un’area specifica della View. Per aree rettangolari, usa Rect.contains(x, y). Per cerchi — verifica della distanza dal centro: sqrt(dx^2 + dy^2) < radius. Per forme arbitrarie — Path.op() o Region.contains().

Multitouch viene gestito tramite MotionEvent.getPointerCount() e getPointerId(index). Ogni dito riceve un ID unico che viene conservato da ACTION_DOWN a ACTION_POINTER_UP. ACTION_MOVE può contenere dati di tutte le dita attive — usa getHistoricalX/Y per l’interpolazione del movimento.

Ottimizzazione delle prestazioni di Custom View

Custom View può diventare un collo di bottiglia delle prestazioni se onDraw viene chiamato frequentemente (animazione, scorrimento) o contiene operazioni pesanti. Android fornisce diversi meccanismi per ottimizzare il rendering dei componenti personalizzati.

Tipo di layer per disegno complesso

View.setLayerType consente di passare il rendering della View a un layer software (LAYER_TYPE_SOFTWARE) o hardware (LAYER_TYPE_HARDWARE). Il layer software è utile per grafica Canvas complessa non supportata dall’accelerazione hardware — ad esempio, drawTextOnPath o effetti Path complessi.

Invalidare solo l’area necessaria

invalidate(Rect) ridisegna solo l’area specificata della View, non l’intero componente. Questo è criticamente importante per i grandi Custom View (grafica, mappe, canvas di disegno), dove il ridisegno completo di ogni frame causa cali di FPS. Usa postInvalidateOnAnimation() per la sincronizzazione con vsync.

kotlin
class EfficientCustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    // Oggetti creati una volta nel costruttore
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rect = Rect()
    private val position = PointF()

    fun updatePosition(x: Float, y: Float) {
        position.set(x, y)
        // Disegna solo l’area intorno al punto
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Accelerazione hardware è abilitata per impostazione predefinita su Android 3.0+ (API 11+). Le operazioni Canvas drawCircle, drawRect, drawBitmap sono accelerate via hardware ed eseguite sulla GPU. Tuttavia, drawTextOnPath, drawVertices, operazioni di clipping complesse non sono accelerate — usa LAYER_TYPE_SOFTWARE per esse.

Secondo Android Performance Patterns (Google, 2023), le principali cause di rallentamento di Custom View — allocazioni in onDraw (creazione di oggetti ogni frame), ridisegno dell’intera View al cambio di un elemento e assenza di LayerType.HARDWARE per contenuti statici. Correggere questi tre problemi aumenta gli FPS da 30 a 60 nella maggior parte degli scenari.

MetodoScopoFrequenza di chiamata
onDrawDisegnare il contenuto della ViewA ogni invalidate()
onMeasureDeterminare le dimensioni della ViewAl cambio di layout
onLayoutPosizionare le View figlieDopo onMeasure
onTouchEventGestione dei tocchiA ogni tocco
onSizeChangedReazione al cambio di dimensioneAl primo layout e cambio

Domande frequenti

Quando utilizzare Custom View invece di Compose?

Custom View (Canvas API) è giustificato quando sono necessarie prestazioni di disegno personalizzato massime (grafica, editor video, mappe), integrazione con codice esistente basato su View o supporto per versioni Android inferiori all’API 21. Jetpack Compose è un approccio moderno per la maggior parte dei nuovi progetti, che utilizza UI dichiarativa ed è più adatto per interfacce dinamiche.

È obbligatorio sovrascrivere onMeasure?

Sì, se la View utilizza wrap_content nel layout XML. Senza onMeasure, wrap_content si comporterà come match_parent perché l’implementazione predefinita di View.onMeasure non imposta una dimensione predefinita. Se la View ha sempre una dimensione fissa o match_parent, onMeasure può non essere sovrascritto.

Come animare un Custom View?

Usa ValueAnimator o ObjectAnimator per modificare le proprietà della View (colore, raggio, posizione) e chiama invalidate() nel callback dell’animazione per ridisegnare. ValueAnimator viene eseguito sul thread principale ed è sincronizzato con vsync. Per animazioni complesse (fisica, particelle), usa Choreographer.FrameCallback o Android Animation Framework.

Come gestire il multitouch in Custom View?

In onTouchEvent, usa MotionEvent.getPointerCount() per determinare il numero di dita e getPointerId(i) per tracciare ogni dito tramite ID unico. ACTION_POINTER_DOWN / ACTION_POINTER_UP — eventi di aggiunta/rimozione dito. Per il pizzicamento-zoom, usa ScaleGestureDetector in coppia con onTouchEvent — semplifica il rilevamento del pizzicamento.

Si può usare Custom View in Jetpack Compose?

Sì, tramite AndroidView — una funzione composable che incorpora un elemento basato su View nella gerarchia di Compose. AndroidView accetta una factory per creare la View e un callback update per la sincronizzazione dello stato. Ciò consente di riutilizzare Custom View esistenti in nuovi progetti Compose senza riscrittura completa.

Riepilogo

  • Custom View — un componente Android personalizzato creato ereditando da View con sovrascrittura di onDraw, onMeasure e onTouchEvent per disegno e gestione personalizzati.
  • onDraw e Canvas API — le basi del disegno: forme geometriche, testo, Bitmap, percorsi — configurati tramite Paint per colore, stile e carattere.
  • onMeasure è importante per il corretto comportamento di wrap_content; usa MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) e resolveSize per calcolare le dimensioni.
  • Gli attributi personalizzati tramite attrs.xml e TypedArray consentono di configurare Custom View dal markup XML con supporto all’autocompletamento in Android Studio.
  • onTouchEvent con GestureDetector fornisce la gestione di tocchi, gesti e multitouch, mentre l’hit-testing determina l’area di interazione.
  • Prestazioni: crea gli oggetti una volta nel costruttore, usa invalidate(Rect) per il ridisegno parziale e LayerType per effetti complessi.
  • Custom View si integra con Jetpack Compose tramite AndroidView, consentendo di riutilizzare componenti pronti in nuovi progetti Compose.

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.

Discuti il progetto

Leggi anche