View Lifecycle — cos’è, processi onMeasure onLayout onDraw

Autore: IT Sectr Pubblicato: 2026-03-05 Tempo di lettura: 10 min

View Lifecycle — la sequenza di metodi che Android chiama per disegnare e ridisegnare un elemento dell’interfaccia utente (View) sullo schermo. A differenza di Activity o Fragment, View è un componente leggero che non ha un ciclo di vita esteso, ma attraversa un rigoroso processo trifasico: onMeasure (misurazione), onLayout (posizionamento), onDraw (disegno). Comprendere View Lifecycle è necessario per creare View personalizzate, ottimizzare le prestazioni e risolvere problemi di disegno. Secondo Google, le View personalizzate accelerano l’interfaccia utente del 15–40% rispetto a una combinazione di ViewGroups annidati standard quando implementate correttamente. La documentazione Android sulle View personalizzate descrive onMeasure, onLayout e onDraw come i tre pilastri di View Lifecycle.

Punti chiave

  • View Lifecycle si compone di tre fasi: onMeasure (dimensioni), onLayout (posizioni), onDraw (disegno) — e viene attivato da invalidate() o requestLayout().
  • onMeasure calcola la larghezza e l’altezza di una View in base a MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout dispone le View figlie all’interno di una ViewGroup, determinando le loro coordinate left, top, right, bottom.
  • onDraw renderizza il contenuto della View su Canvas: sfondo, testo, forme, immagini.
  • Un View Lifecycle errato è la causa principale di problemi di prestazioni dell’interfaccia utente (jank, frame persi) e problemi di gerarchia.

View Lifecycle — cos’è in Android

View Lifecycle è il processo che una View Android (e ViewGroup) attraversa per visualizzarsi sullo schermo. A differenza di Activity o Fragment, View non ha onStart/onStop/onDestroy — la sua “vita” consiste in un processo ciclico di misurazione, posizionamento e disegno. Questo ciclo viene attivato ogni volta che una View deve essere visualizzata o ridisegnata.

Le tre fasi di View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — determina le dimensioni desiderate della View. Il sistema passa MeasureSpec — un’istruzione su quali dimensioni sono consentite (valore esatto, massimo o senza restrizioni).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — posiziona la View e i suoi figli sullo schermo. Per una View, definisce i propri confini; per una ViewGroup, posiziona gli elementi figli.
  • onDraw(Canvas canvas) — disegna il contenuto della View sul Canvas fornito. Il sistema fornisce un Canvas che traduce i comandi in bitmap o GPU.

Il ciclo completo di View Lifecycle include anche metodi relativi all’attaccamento di una View a una finestra: onAttachedToWindow (la View è attaccata a una finestra, ha accelerazione HW) e onDetachedFromWindow (la View è staccata, le risorse vengono liberate). Questi metodi vengono chiamati una volta per vita della View e sono importanti per registrare/cancellare animazioni e sensori.

Secondo Android Performance Blog, 65% dei problemi di prestazioni dell’interfaccia utente (jank, frame persi) sono correlati a un’implementazione errata di onMeasure e onDraw: override eccessivo, chiamata non necessaria di requestLayout(), creazione di oggetti in onDraw.

onMeasure: misurare le dimensioni della View

onMeasure — la fase più importante e più complessa di View Lifecycle. In questa fase, Android determina quanto spazio la View occuperà sullo schermo. Il sistema passa MeasureSpec — istruzioni impacchettate in int costituite da una modalità e una dimensione.

Le tre modalità di MeasureSpec:

ModalitàCostanteSignificatoEsempio
EXACTLYMeasureSpec.EXACTLYDimensione esatta impostata dal genitore (match_parent o larghezza fissa)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTLa View può avere fino alla dimensione massima specificata (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDSenza restrizioni — la View può avere qualsiasi dimensione (ScrollView, RecyclerView)larghezza illimitata → MeasureSpec(0, UNSPECIFIED)

L’implementazione di onMeasure deve:

  • Chiamare setMeasuredDimension(int width, int height) per salvare le dimensioni misurate.
  • Considerare il padding — sottrarre getPaddingLeft() + getPaddingRight() dalla larghezza disponibile.
  • Per ViewGroup — misurare tutti i figli tramite measureChild() o measureChildWithMargins().
  • Per wrap_content — calcolare la dimensione in base al contenuto (testo, immagine).
  • Non chiamare requestLayout() all’interno di onMeasure — ciò causerebbe un ciclo infinito.

Errore tipico: non considerare MeasureSpec quando si usa wrap_content. Se una View è impostata su wrap_content, ma onMeasure non gestisce AT_MOST e restituisce una dimensione fissa, la View verrà ritagliata o occuperà più spazio del necessario.

onLayout: posizionare le View sullo schermo

onLayout — la fase in cui una View o ViewGroup dispone i suoi figli all’interno dei propri confini. Per una View normale (non ViewGroup), onLayout non è richiesto — il sistema chiama layout() con i parametri passati dal genitore. Per una ViewGroup, onLayout è obbligatorio — senza di esso, le View figlie non verranno posizionate.

Firma di onLayout:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // disposizione delle View figlie
}

Il parametro changed indica se la posizione o la dimensione della View è cambiata rispetto al layout precedente. Se false, la View può saltare il ricalcolo delle posizioni dei figli per ottimizzare.

Per ViewGroup, onLayout deve:

  • Iterare su tutti i figli tramite getChildCount() e getChildAt(i).
  • Per ogni figlio, determinare left, top, right, bottom — coordinate all’interno di ViewGroup (considerando il padding).
  • Chiamare child.layout(l, t, r, b) per ogni figlio.
  • Considerare gravity, margins, alignment.

onLayout viene chiamato dopo onMeasure — le dimensioni misurate sono disponibili tramite getMeasuredWidth()/getMeasuredHeight(). Se una View figlia ha dimensioni effettive diverse dopo layout(), viene chiamato requestLayout() per rimisurare. Questo è chiamato “passaggio di layout” e può innescare una reazione a catena di ricalcoli.

onDraw: disegnare il contenuto del Canvas

onDraw — la fase in cui una View si disegna sul Canvas. Questa è l’unica fase che può essere chiamata più volte senza onMeasure e onLayout — se la View è marcata come invalidate(). Canvas fornisce API di disegno: drawLine, drawRect, drawCircle, drawText, drawBitmap e drawPath.

Regole di onDraw:

  • Non creare oggetti in onDraw — ogni chiamata a onDraw dovrebbe utilizzare oggetti pre-creati (Path, Paint, Rect). Creare oggetti in onDraw causa pause del GC e frame persi.
  • Non chiamare requestLayout() o invalidate() all’interno di onDraw — ciò innescherebbe un ciclo infinito di ridisegno.
  • Non eseguire calcoli lunghi — onDraw viene eseguito sul thread UI. I calcoli complessi dovrebbero essere spostati su un thread in background o pre-calcolati.
  • Utilizzare l’accelerazione hardware — dall’API 14+, Canvas può funzionare tramite GPU. Per grafiche complesse (gradienti, ombre, rotazioni), l’accelerazione HW offre fino al 300% di miglioramento delle prestazioni.
  • Disegnare solo l’area visibile — utilizzare canvas.clipRect() per ritagliare le parti invisibili.

Ordine di disegno in ViewGroup: sfondo (setBackgroundDrawable) → onDraw (contenuto) → dispatchDraw (View figlie) → onDrawForeground (primo piano). dispatchDraw chiama onDraw di ogni figlio. L’override di dispatchDraw viene utilizzato per applicare effetti sopra gli elementi figli.

Secondo le statistiche di Android Vitals, le cause più comuni di frame persi in onDraw sono la creazione di oggetti all’interno del metodo (48%), la chiamata a decodeResource (22%) e le operazioni complesse su Path senza caching (15%).

Invalidazione: quando una View viene ridisegnata

Invalidazione — il meccanismo che innesca il ridisegno della View. Chiamare invalidate() marca la View come “sporca” e programma la chiamata di onDraw nel prossimo ciclo di disegno. Chiamare requestLayout() è un’operazione più “pesante”, che innesca il ciclo completo: onMeasure → onLayout → onDraw.

MetodoCosa faQuando usarlo
invalidate()Innesca onDraw senza onMeasure/onLayoutSolo l’aspetto è cambiato (colore, testo, progresso)
invalidate(Rect)Ridisegna solo l’area specificataParte della View è cambiata — animazione, selezione
postInvalidate()Chiama invalidate da un thread non UIThread in background ha aggiornato i dati per il disegno
requestLayout()Innesca onMeasure → onLayout → onDrawLa dimensione del contenuto è cambiata (testo, immagine)
forceLayout()Marca la View per rimisurazione forzataLo stato interno è cambiato, la dimensione potrebbe essere cambiata

Animazioni e View Lifecycle: ViewPropertyAnimator e ValueAnimator chiamano invalidate() su ogni frame di animazione. ObjectAnimator chiama un setter sulla View, che se il setter modifica la dimensione (larghezza/altezza), chiama automaticamente requestLayout(). Questo può essere costoso per ViewGroups complesse: ogni requestLayout innesca l’intera gerarchia fino alla vista radice.

Regola di ottimizzazione: invalidate() invece di requestLayout() ovunque cambi solo l’aspetto (colore, trasparenza, rotazione senza cambiamento di dimensione). Utilizzare requestLayout solo quando si modificano dimensioni o contenuti che influenzano la dimensione.

Ottimizzazione delle View personalizzate: best practice

Le View personalizzate sono uno strumento potente per creare interfacce utente uniche, ma richiedono il rigoroso rispetto delle regole di prestazione. Ecco le principali raccomandazioni di Google per l’ottimizzazione di View Lifecycle.

  • Pre-calcolare tutto ciò che può essere calcolato — dimensioni, coordinate, percorso, colori del gradiente. In onDraw, eseguire solo il disegno.
  • Memorizzare nella cache i risultati di misurazione — se una View ha dimensioni fisse, salvare MeasureSpec e restituire setMeasuredDimension senza calcoli aggiuntivi.
  • Utilizzare ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — per la gestione dei tocchi.
  • Minimizzare il numero di View nella gerarchia — le View personalizzate che combinano più elementi sono sempre più veloci di una ViewGroup con 3–5 View annidate. Google raccomanda non più di 10 View annidate per schermo.
  • Utilizzare ConstraintLayout per gerarchia piatta — costruisce un’unica ViewGroup con prestazioni vicine a RelativeLayout, ma senza annidamento.
  • Disabilitare il layer hardware dopo il ridisegno — utilizzare setLayerType(LAYER_TYPE_HARDWARE) per View con animazioni e setLayerType(LAYER_TYPE_NONE) dopo il completamento.
  • Utilizzare invalidate() con Rect — ridisegnare solo l’area modificata, non l’intera View.
  • Evitare l’overdraw — utilizzare Profile GPU Rendering in Android Studio per identificare ridisegni non necessari. L’overdraw medio per le app Google è 1.5x, il massimo raccomandato è 2.5x.

Esempi di codice View in Kotlin

Esempio 1: View personalizzata — indicatore di progresso

Un semplice indicatore di progresso circolare con corretta implementazione di onMeasure, onDraw e invalidate.

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

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        style = Paint.Style.STROKE
        strokeWidth = 8f
        strokeCap = Paint.Cap.ROUND
    }

    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 8f
    }

    private var progress = 0f
    private var viewWidth = 0
    private var viewHeight = 0

    fun setProgress(value: Float) {
        progress = value.coerceIn(0f, 100f)
        invalidate()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val desiredSize = 100 * resources.displayMetrics.density.toInt()
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        val size = minOf(width, height).coerceAtLeast(desiredSize)
        setMeasuredDimension(size, size)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val padding = progressPaint.strokeWidth / 2
        val radius = (minOf(viewWidth, viewHeight) - padding) / 2
        val cx = viewWidth / 2f
        val cy = viewHeight / 2f
        canvas.drawCircle(cx, cy, radius, backgroundPaint)
        val sweepAngle = (progress / 100f) * 360f
        canvas.drawArc(cx - radius, cy - radius, cx + radius, cy + radius,
            -90f, sweepAngle, false, progressPaint)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
    }
}

Barra di progresso circolare: onMeasure restituisce una dimensione quadrata basata su MeasureSpec, onSizeChanged ricorda le dimensioni, onDraw disegna lo sfondo e l’arco di progresso. Invalidate viene chiamato quando il progresso cambia — onMeasure/onLayout non vengono influenzati. Paint viene creato una volta nel costruttore, non in onDraw.

Esempio 2: ViewGroup — FlowLayout semplice

Una ViewGroup personalizzata che dispone le View figlie in righe (come Flexbox wrap).

kotlin
class FlowLayout constructor(
    context: Context, attrs: AttributeSet? = null
) : ViewGroup(context, attrs) {

    private val horizontalSpacing = 8.dpToPx(resources)
    private val verticalSpacing = 8.dpToPx(resources)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = MeasureSpec.getSize(widthMeasureSpec)
        var totalHeight = paddingTop + paddingBottom
        var rowWidth = paddingLeft
        var rowHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight)
            if (rowWidth + child.measuredWidth > width - paddingRight) {
                totalHeight += rowHeight + verticalSpacing
                rowWidth = paddingLeft
                rowHeight = 0
            }
            rowWidth += child.measuredWidth + horizontalSpacing
            rowHeight = maxOf(rowHeight, child.measuredHeight)
        }
        totalHeight += rowHeight
        setMeasuredDimension(
            MeasureSpec.getSize(widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec)
        )
    }

    override fun onLayout(changed: Boolean,
        l: Int, t: Int, r: Int, b: Int) {
        var rowTop = paddingTop
        var rowLeft = paddingLeft
        var rowHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (rowLeft + child.measuredWidth > r - paddingRight) {
                rowTop += rowHeight + verticalSpacing
                rowLeft = paddingLeft
                rowHeight = 0
            }
            child.layout(rowLeft, rowTop, rowLeft + child.measuredWidth, rowTop + child.measuredHeight)
            rowLeft += child.measuredWidth + horizontalSpacing
            rowHeight = maxOf(rowHeight, child.measuredHeight)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context, attrs)
    }
}

FlowLayout esegue l’override di onMeasure: misura ogni figlio, passa a una nuova riga quando la larghezza viene superata, calcola l’altezza totale. onLayout posiziona i figli per coordinate considerando le interruzioni di riga. generateLayoutParams restituisce MarginLayoutParams per supportare i margini sulle View figlie.

Esempio 3: onDraw con caching di Path

Una View personalizzata disegna una curva di Bezier morbida, pre-calcolando il Path e memorizzandolo nella cache.

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

    private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#4A90D9")
        style = Paint.Style.FILL
    }

    private val wavePath = Path()
    private var isPathDirty = true
    private var viewWidth = 0
    private var viewHeight = 0

    fun refreshWave() {
        isPathDirty = true
        invalidate()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
        isPathDirty = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (isPathDirty) {
            wavePath.reset()
            val amplitude = viewHeight * 0.1f
            wavePath.moveTo(0f, viewHeight * 0.5f)
            for (x in 0..viewWidth step 4) {
                val y = viewHeight * 0.5f + amplitude * Math.sin(x * 2 * Math.PI / viewWidth).toFloat()
                wavePath.lineTo(x.toFloat(), y)
            }
            wavePath.lineTo(viewWidth.toFloat(), viewHeight.toFloat())
            wavePath.lineTo(0f, viewHeight.toFloat())
            wavePath.close()
            isPathDirty = false
        }
        canvas.drawPath(wavePath, wavePaint)
    }
}

Caching di Path: isPathDirty = true solo quando le dimensioni della View cambiano o viene chiamato refreshWave(). In onDraw, Path viene ricalcolato solo se è “sporco”. Ciò impedisce il ricalcolo della curva di Bezier ad ogni frame di animazione, risparmiando CPU.

Domande frequenti

In che modo View Lifecycle si differenzia da Activity Lifecycle?

View Lifecycle è un processo di disegno ciclico (onMeasure → onLayout → onDraw) indipendente dalla creazione/distruzione di Activity. View non ha onStart/onStop — o è visibile (attaccata a una finestra) o non lo è. Activity Lifecycle gestisce lo stato del componente dell’applicazione, View Lifecycle gestisce il disegno dell’interfaccia utente.

Che effetto ha requestLayout() sulle prestazioni?

requestLayout() innesca il ciclo completo onMeasure → onLayout → onDraw per l’intero albero delle View dalla radice. Se requestLayout() viene chiamato frequentemente (ad esempio, ad ogni frame di animazione), causa jank e frame persi. Secondo Google, un requestLayout richiede in media 2–5 ms su una ViewGroup di 10 elementi. Per le animazioni, utilizzare invalidate().

Quando viene chiamato onAttachedToWindow?

onAttachedToWindow viene chiamato quando una View viene attaccata a una Window — diventa parte della gerarchia visibile. In questo momento, la View riceve l’accelerazione HW e l’accesso alle risorse della Window (WindowManager, Display). onAttachedToWindow è il luogo appropriato per registrare listener di animazioni e BroadcastReceiver che vivono finché la View è visibile.

Cos’è l’overdraw e come ridurlo?

L’overdraw è una situazione in cui un pixel viene disegnato più volte in un singolo frame. Ogni passaggio extra spreca tempo GPU. Metodi di riduzione: impostare windowBackground nel tema (non disegnare lo sfondo nel layout), utilizzare canvas.clipRect(), evitare di unire sfondi annidati, utilizzare ConstraintLayout invece di LinearLayout annidato. Android Studio → Profile GPU Rendering → Overdraw mostra una mappa a colori dell’overdraw (blu = 1x, rosso = 3x+).

super.onDraw() è necessario in una View personalizzata?

Sì, se la View ha uno sfondo. super.onDraw() disegna lo sfondo della View. Se la tua View personalizzata non ha sfondo o disegni il tuo sfondo, super.onDraw() può essere omesso — questo risparmia un passaggio di disegno. Per ViewGroup, super.dispatchDraw() è obbligatorio — disegna le View figlie.

Riepilogo

  • View Lifecycle — tre fasi di disegno: onMeasure (dimensioni), onLayout (posizione), onDraw (rendering).
  • onMeasure elabora MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) e chiama setMeasuredDimension.
  • onLayout in ViewGroup posiziona le View figlie con coordinate left/top/right/bottom.
  • onDraw renderizza il contenuto su Canvas — non creare oggetti all’interno di questo metodo.
  • invalidate() innesca solo onDraw, requestLayout() innesca il ciclo completo onMeasure → onLayout → onDraw.
  • Le View personalizzate accelerano l’interfaccia utente del 15–40%, ma richiedono una corretta implementazione di onMeasure e il caching degli oggetti in onDraw.
  • Per grafiche complesse, utilizzare l’accelerazione hardware e memorizzare nella cache Path/Bitmap.

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