onLayout() é um método da classe ViewGroup que determina as posições e tamanhos das Views filhas no plano de coordenadas do contêiner pai. O sistema Android chama onLayout após a fase de medição (onMeasure), quando a largura e altura medidas já são conhecidas para cada View filha. De acordo com a Documentação para Desenvolvedores Android (2026), onLayout é um método obrigatório de sobrescrever em qualquer ViewGroup personalizada, pois a implementação padrão da ViewGroup não realiza o posicionamento automático dos filhos.
Pontos Principais
onLayout(boolean changed, int l, int t, int r, int b) é um método protected da classe ViewGroup que o sistema chama para posicionar as Views filhas dentro do contêiner pai. O desenvolvedor sobrescreve este método ao criar uma ViewGroup personalizada com uma disposição não padrão de elementos: cascata, grade, padrão xadrez ou por coordenadas arbitrárias. Cada View filha recebe seus limites finais através de uma chamada a child.layout().
O parâmetro changed indica se a posição ou tamanho da própria ViewGroup mudou desde o último layout. Se changed for true, todos os elementos filhos provavelmente também precisam de reposicionamento. Os parâmetros l, t, r, b são as coordenadas dos cantos superior esquerdo e inferior direito da ViewGroup no sistema de coordenadas do seu pai. Dentro de onLayout, o desenvolvedor usa estes valores como coordenadas iniciais para a disposição dos filhos.
ViewGroup é a única classe que sobrescreve onLayout. Uma View normal (não ViewGroup) não tem elementos filhos e não precisa de onLayout — seu posicionamento é tratado pelo contêiner pai. Mesmo que uma View normal sobrescreva onLayout, o sistema não o chamará. Esta é uma diferença fundamental do onMeasure, que é chamado para qualquer View.
A fase layout começa com uma chamada ao método público layout(int l, int t, int r, int b) na View raiz. Este método define as coordenadas finais da própria View e chama onLayout se a View for uma ViewGroup. Então onLayout chama recursivamente child.layout() para cada elemento filho, e o processo se repete descendo na hierarquia. Assim, o layout se propaga da raiz para as folhas.
Antes de chamar onLayout, o sistema verifica se as dimensões da View mudaram em comparação com o ciclo anterior. Se as dimensões não mudaram e requestLayout não foi chamado, onLayout pode não ser chamado — o sistema usa os resultados do layout anterior. Esta é uma otimização que evita recálculos desnecessários de posições durante animações ou rolagem, quando apenas o conteúdo muda mas não as dimensões.
requestLayout() é um método de View que notifica o sistema que o layout da View está desatualizado e precisa ser recalculado. Chamar requestLayout desencadeia um ciclo completo: primeiro onMeasure é chamado, depois onLayout, depois onDraw. Ao contrário de invalidate, que apenas aciona o redesenhamento, requestLayout aciona um recálculo completo de dimensões e posições. Chamadas excessivas a requestLayout são uma causa comum de problemas de desempenho.
l (left) — a coordenada X da borda esquerda da ViewGroup no sistema de coordenadas do seu pai. t (top) — a coordenada Y da borda superior. r (right) — a coordenada X da borda direita. b (bottom) — a coordenada Y da borda inferior. A largura da ViewGroup é calculada como r - l, a altura como b - t. Estas coordenadas já incluem todo o padding da própria ViewGroup.
Dentro de onLayout, o desenvolvedor chama child.layout(int childLeft, int childTop, int childRight, int childBottom) para cada View filha. As coordenadas passadas para child.layout devem estar no sistema de coordenadas da ViewGroup pai. Normalmente childLeft e childTop são calculados levando em conta o padding do pai: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parâmetro | Descrição | Uso Típico |
|---|---|---|
| l (left) | Coordenada da borda esquerda da ViewGroup no pai | Ponto inicial no eixo X para elementos filhos |
| t (top) | Coordenada da borda superior da ViewGroup no pai | Ponto inicial no eixo Y para elementos filhos |
| r (right) | Coordenada da borda direita da ViewGroup no pai | Limite superior de largura, r - l = getWidth() |
| b (bottom) | Coordenada da borda inferior da ViewGroup no pai | Limite superior de altura, b - t = getHeight() |
As coordenadas filhas são calculadas pela fórmula: childLeft = l + paddingLeft + (marginLeft se presente), childRight = childLeft + child.getMeasuredWidth(). Similarmente para o eixo vertical: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Após calcular estes quatro valores, child.layout(childLeft, childTop, childRight, childBottom) é chamado.
Vamos criar um FlowLayout — uma ViewGroup personalizada que organiza as Views filhas em linhas, movendo elementos para uma nova linha quando a linha atual está cheia. É um equivalente do Flexbox com wrap em um único plano. onLayout itera por todas as Views filhas, calcula a posição para cada uma e chama child.layout() com os limites corretos.
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 são duas fases sequenciais do ciclo de vida da View que realizam tarefas fundamentalmente diferentes. onMeasure determina as dimensões desejadas (medidas) de uma View, enquanto onLayout define as coordenadas e dimensões reais (finais). A diferença chave: em onMeasure, as dimensões podem ser intermediárias e posteriormente ajustadas pelo pai, enquanto em onLayout a posição final de cada View filha é fixada.
onMeasure é chamado para cada View, incluindo Views folha (TextView, ImageView, Button). onLayout é chamado apenas para ViewGroup. Isso porque o posicionamento é responsabilidade do contêiner pai, não da própria View. Uma View folha recebe sua posição através de layout() chamado a partir do onLayout do pai.
getMeasuredWidth() e getMeasuredHeight() estão disponíveis após onMeasure, enquanto getWidth() e getHeight() estão disponíveis apenas após onLayout. Se você acessar getWidth() dentro de onMeasure, ele retornará o valor do ciclo anterior ou zero. Portanto, para calcular dimensões em onMeasure, você deve usar MeasureSpec e os filhos sequencialmente.
Posicionamento sem levar em conta o padding — o primeiro erro ao implementar onLayout. O desenvolvedor frequentemente esquece de adicionar o paddingLeft e paddingTop do pai às coordenadas iniciais das Views filhas. Como resultado, os filhos são exibidos na borda da ViewGroup, ignorando o padding definido via setPadding() ou na marcação XML. Cálculo correto: childLeft = paddingLeft + offsetX.
Chamar layout para filhos invisíveis — o segundo problema comum. Se uma ViewGroup contém Views filhas com visibilidade GONE, elas não precisam ser posicionadas — não ocupam espaço. No entanto, onLayout deve lidar com este caso corretamente, pulando filhos GONE. Para filhos INVISIBLE, ainda é necessário chamar layout — eles mantêm seu espaço mesmo não sendo exibidos.
Ignorar o parâmetro changed — o terceiro erro. O parâmetro changed indica se as dimensões ou posição da ViewGroup mudaram. Se changed == false, coordenadas em cache podem ser usadas sem recalcular o layout de todos os elementos filhos. No entanto, o cache completo de layout é uma tarefa complexa, e na maioria das implementações onLayout simplesmente recalcula todos os elementos a cada vez. Isso é aceitável com um número pequeno de filhos.
Perguntas Frequentes
Sim, é possível se a ViewGroup usar LayoutParams padrão e não adicionar lógica de posicionamento personalizada. No entanto, a implementação padrão de onLayout em ViewGroup não realiza nenhuma ação — os elementos filhos não serão posicionados. Na prática, todas as ViewGroup (LinearLayout, RelativeLayout, FrameLayout) sobrescrevem onLayout.
layout() é um método público final de View, chamado pelo sistema ou ViewGroup pai. Ele define as coordenadas da própria View e chama onLayout se a View for uma ViewGroup. onLayout() é um método protected que o desenvolvedor sobrescreve para a disposição personalizada de elementos filhos.
Tecnicamente — sim, pode. Mas isso não é recomendado, pois leva a recursão infinita: requestLayout → onMeasure → onLayout → requestLayout. Se requestLayout for chamado dentro de onLayout, o sistema lançará uma exceção StackOverflowError. Todas as alterações de dimensões devem ser feitas antes de onLayout.
Animações de layout (LayoutTransition) interceptam mudanças nas posições das Views filhas e aplicam animação de transição. Quando LayoutTransition está habilitado, onLayout primeiro define as posições finais, então LayoutTransition anima o movimento da posição antiga para a nova. Isso requer uma implementação correta de onLayout com coordenadas finais adequadas.
invalidate() aciona apenas a fase draw (redesenhamento), sem afetar measure e layout. Para acionar onLayout, você precisa chamar requestLayout(), que inicia o ciclo completo: measure → layout → draw. invalidate é mais eficiente para atualizar a aparência quando dimensões e posições não mudam.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também