onLayout() è un metodo della classe ViewGroup che determina le posizioni e le dimensioni delle View figlie sul piano di coordinate del contenitore padre. Il sistema Android chiama onLayout dopo la fase di misurazione (onMeasure), quando la larghezza e l'altezza misurate sono già note per ogni View figlia. Secondo la Documentazione per Sviluppatori Android (2026), onLayout è un metodo obbligatorio da sovrascrivere in qualsiasi ViewGroup personalizzata, poiché l'implementazione standard di ViewGroup non esegue il posizionamento automatico dei figli.
Punti Chiave
onLayout(boolean changed, int l, int t, int r, int b) è un metodo protected della classe ViewGroup che il sistema chiama per posizionare le View figlie all'interno del contenitore padre. Lo sviluppatore sovrascrive questo metodo quando crea una ViewGroup personalizzata con una disposizione non standard di elementi: a cascata, a griglia, a scacchiera o con coordinate arbitrarie. Ogni View figlia riceve i suoi confini finali tramite una chiamata a child.layout().
Il parametro changed indica se la posizione o la dimensione della ViewGroup stessa è cambiata dall'ultimo layout. Se changed è true, tutti gli elementi figli probabilmente necessitano di un riposizionamento. I parametri l, t, r, b sono le coordinate degli angoli superiore sinistro e inferiore destro della ViewGroup nel sistema di coordinate del suo padre. All'interno di onLayout, lo sviluppatore usa questi valori come coordinate iniziali per la disposizione dei figli.
ViewGroup è l'unica classe che sovrascrive onLayout. Una View normale (non ViewGroup) non ha elementi figli e non necessita di onLayout — il suo posizionamento è gestito dal contenitore padre. Anche se una View normale sovrascrive onLayout, il sistema non lo chiamerà. Questa è una differenza fondamentale da onMeasure, che viene chiamato per qualsiasi View.
La fase layout inizia con una chiamata al metodo pubblico layout(int l, int t, int r, int b) sulla View radice. Questo metodo imposta le coordinate finali della View stessa e chiama onLayout se la View è una ViewGroup. Poi onLayout chiama ricorsivamente child.layout() per ogni elemento figlio, e il processo si ripete scendendo lungo la gerarchia. Così, il layout si propaga dalla radice alle foglie.
Prima di chiamare onLayout, il sistema verifica se le dimensioni della View sono cambiate rispetto al ciclo precedente. Se le dimensioni non sono cambiate e requestLayout non è stato chiamato, onLayout potrebbe non essere chiamato — il sistema usa i risultati del layout precedente. Questa è un'ottimizzazione che previene ricalcoli non necessari di posizioni durante animazioni o scorrimento, quando cambia solo il contenuto ma non le dimensioni.
requestLayout() è un metodo di View che notifica al sistema che il layout della View è obsoleto e deve essere ricalcolato. Chiamare requestLayout attiva un ciclo completo: prima viene chiamato onMeasure, poi onLayout, poi onDraw. A differenza di invalidate, che attiva solo il ridisegno, requestLayout attiva un ricalcolo completo di dimensioni e posizioni. Le chiamate eccessive a requestLayout sono una causa comune di problemi di prestazioni.
l (left) — la coordinata X del bordo sinistro della ViewGroup nel sistema di coordinate del suo padre. t (top) — la coordinata Y del bordo superiore. r (right) — la coordinata X del bordo destro. b (bottom) — la coordinata Y del bordo inferiore. La larghezza della ViewGroup è calcolata come r - l, l'altezza come b - t. Queste coordinate includono già tutto il padding della ViewGroup stessa.
All'interno di onLayout, lo sviluppatore chiama child.layout(int childLeft, int childTop, int childRight, int childBottom) per ogni View figlia. Le coordinate passate a child.layout devono essere nel sistema di coordinate della ViewGroup padre. Tipicamente childLeft e childTop sono calcolati tenendo conto del padding del padre: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parametro | Descrizione | Uso Tipico |
|---|---|---|
| l (left) | Coordinata del bordo sinistro della ViewGroup nel padre | Punto iniziale sull'asse X per gli elementi figli |
| t (top) | Coordinata del bordo superiore della ViewGroup nel padre | Punto iniziale sull'asse Y per gli elementi figli |
| r (right) | Coordinata del bordo destro della ViewGroup nel padre | Limite superiore di larghezza, r - l = getWidth() |
| b (bottom) | Coordinata del bordo inferiore della ViewGroup nel padre | Limite superiore di altezza, b - t = getHeight() |
Le coordinate figlie sono calcolate con la formula: childLeft = l + paddingLeft + (marginLeft se presente), childRight = childLeft + child.getMeasuredWidth(). Analogamente per l'asse verticale: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Dopo aver calcolato questi quattro valori, viene chiamato child.layout(childLeft, childTop, childRight, childBottom).
Creiamo un FlowLayout — una ViewGroup personalizzata che dispone le View figlie in righe, spostando gli elementi su una nuova riga quando la riga corrente è piena. È un equivalente di Flexbox con wrap in un unico piano. onLayout itera attraverso tutte le View figlie, calcola la posizione per ciascuna e chiama child.layout() con i confini corretti.
class FlowLayout(context: Context)
: ViewGroup(context) {
private val horizontalSpacing = 12
private val verticalSpacing = 8
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val parentWidth =
MeasureSpec.getSize(widthMeasureSpec)
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec, 0,
heightMeasureSpec, 0)
if (rowX + child.measuredWidth >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
rowX += child.measuredWidth +
horizontalSpacing
maxRowHeight = maxOf(maxRowHeight,
child.measuredHeight)
}
val totalHeight = rowY + maxRowHeight +
paddingBottom
setMeasuredDimension(
resolveSize(parentWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
val parentWidth = r - l
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
val ch = child.measuredHeight
if (rowX + cw >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
child.layout(rowX, rowY,
rowX + cw, rowY + ch)
rowX += cw + horizontalSpacing
maxRowHeight =
maxOf(maxRowHeight, ch)
}
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams =
MarginLayoutParams(context, attrs)
}
onMeasure e onLayout sono due fasi sequenziali del ciclo di vita di View che svolgono compiti fondamentalmente diversi. onMeasure determina le dimensioni desiderate (misurate) di una View, mentre onLayout imposta le coordinate e dimensioni effettive (finali). La differenza chiave: in onMeasure, le dimensioni possono essere intermedie e successivamente aggiustate dal padre, mentre in onLayout viene fissata la posizione finale di ogni View figlia.
onMeasure viene chiamato per ogni View, incluse le View foglia (TextView, ImageView, Button). onLayout viene chiamato solo per ViewGroup. Questo perché il posizionamento è responsabilità del contenitore padre, non della View stessa. Una View foglia riceve la sua posizione attraverso layout() chiamato dal onLayout del padre.
getMeasuredWidth() e getMeasuredHeight() sono disponibili dopo onMeasure, mentre getWidth() e getHeight() sono disponibili solo dopo onLayout. Se si accede a getWidth() all'interno di onMeasure, restituirà il valore del ciclo precedente o zero. Pertanto, per calcolare le dimensioni in onMeasure, è necessario usare MeasureSpec e i figli sequenzialmente.
Posizionamento senza considerare il padding — il primo errore nell'implementare onLayout. Lo sviluppatore spesso dimentica di aggiungere il paddingLeft e paddingTop del padre alle coordinate iniziali delle View figlie. Di conseguenza, i figli vengono visualizzati al bordo della ViewGroup, ignorando il padding impostato tramite setPadding() o nel markup XML. Calcolo corretto: childLeft = paddingLeft + offsetX.
Chiamare layout per figli invisibili — il secondo problema comune. Se una ViewGroup contiene View figlie con visibilità GONE, non è necessario posizionarle — non occupano spazio. Tuttavia, onLayout deve gestire correttamente questo caso, saltando i figli GONE. Per i figli INVISIBLE, è comunque necessario chiamare layout — mantengono il loro spazio anche se non vengono visualizzati.
Ignorare il parametro changed — il terzo errore. Il parametro changed indica se le dimensioni o la posizione della ViewGroup sono cambiate. Se changed == false, si possono usare coordinate memorizzate nella cache senza ricalcolare il layout di tutti gli elementi figli. Tuttavia, la memorizzazione completa del layout è un compito complesso, e nella maggior parte delle implementazioni onLayout ricalcola semplicemente tutti gli elementi ogni volta. Questo è accettabile con un numero ridotto di figli.
Domande frequenti
Sì, è possibile se la ViewGroup utilizza LayoutParams standard e non aggiunge logica di posizionamento personalizzata. Tuttavia, l'implementazione standard di onLayout in ViewGroup non esegue alcuna azione — gli elementi figli non verranno posizionati. In pratica, tutte le ViewGroup (LinearLayout, RelativeLayout, FrameLayout) sovrascrivono onLayout.
layout() è un metodo pubblico finale di View, chiamato dal sistema o dalla ViewGroup padre. Imposta le coordinate della View stessa e chiama onLayout se la View è una ViewGroup. onLayout() è un metodo protected che lo sviluppatore sovrascrive per la disposizione personalizzata degli elementi figli.
Tecnicamente — sì, può. Ma questo è fortemente sconsigliato, poiché porta a ricorsione infinita: requestLayout → onMeasure → onLayout → requestLayout. Se requestLayout viene chiamato all'interno di onLayout, il sistema lancerà un'eccezione StackOverflowError. Tutte le modifiche alle dimensioni devono essere eseguite prima di onLayout.
Le animazioni di layout (LayoutTransition) intercettano i cambiamenti nelle posizioni delle View figlie e applicano un'animazione di transizione. Quando LayoutTransition è abilitato, onLayout imposta prima le posizioni finali, poi LayoutTransition anima il movimento dalla posizione vecchia a quella nuova. Questo richiede un'implementazione corretta di onLayout con coordinate finali appropriate.
invalidate() attiva solo la fase draw (ridisegno), senza influenzare measure e layout. Per attivare onLayout, è necessario chiamare requestLayout(), che avvia il ciclo completo: measure → layout → draw. invalidate è più efficiente per aggiornare l'aspetto quando dimensioni e posizioni non cambiano.
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.
Leggi anche