View Lifecycle — o que é, processos onMeasure onLayout onDraw

Autor: IT Sectr Publicado: 2026-03-05 Tempo de leitura: 10 min

View Lifecycle — a sequência de métodos que o Android chama para desenhar e redesenhar um elemento de interface do usuário (View) na tela. Ao contrário de Activity ou Fragment, View é um componente leve que não possui um ciclo de vida estendido, mas passa por um processo rigoroso de três fases: onMeasure (medição), onLayout (posicionamento), onDraw (desenho). Entender o View Lifecycle é necessário para criar Views personalizadas, otimizar o desempenho e resolver problemas de desenho. De acordo com o Google, Views personalizadas aceleram a UI em 15–40% em comparação com uma combinação de ViewGroups aninhados padrão quando implementadas corretamente. Documentação do Android sobre Views personalizadas descreve onMeasure, onLayout e onDraw como os três pilares do View Lifecycle.

Pontos principais

  • View Lifecycle consiste em três fases: onMeasure (tamanhos), onLayout (posições), onDraw (desenho) — e é acionado por invalidate() ou requestLayout().
  • onMeasure calcula a largura e altura de uma View com base em MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout organiza as Views filhas dentro de uma ViewGroup, determinando suas coordenadas left, top, right, bottom.
  • onDraw renderiza o conteúdo da View no Canvas: fundo, texto, formas, imagens.
  • View Lifecycle incorreto é a principal causa de problemas de desempenho da UI (jank, frames perdidos) e problemas de hierarquia.

View Lifecycle — o que é no Android

View Lifecycle é o processo pelo qual uma View do Android (e ViewGroup) passa para se exibir na tela. Ao contrário de Activity ou Fragment, View não tem onStart/onStop/onDestroy — sua “vida” consiste em um processo cíclico de medição, posicionamento e desenho. Este ciclo é acionado sempre que uma View precisa ser exibida ou redesenhada.

As três fases do View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — determina as dimensões desejadas da View. O sistema passa MeasureSpec — uma instrução sobre quais dimensões são permitidas (valor exato, máximo ou sem restrições).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — posiciona a View e seus filhos na tela. Para uma View, define seus próprios limites; para uma ViewGroup, posiciona os elementos filhos.
  • onDraw(Canvas canvas) — desenha o conteúdo da View no Canvas fornecido. O sistema fornece um Canvas que traduz comandos em bitmap ou GPU.

O ciclo completo do View Lifecycle também inclui métodos relacionados à anexação de uma View a uma janela: onAttachedToWindow (View anexada a uma janela, tem aceleração HW) e onDetachedFromWindow (View desanexada, recursos são liberados). Esses métodos são chamados uma vez por vida da View e são importantes para registrar/cancelar animações e sensores.

De acordo com o Android Performance Blog, 65% dos problemas de desempenho da UI (jank, quadros perdidos) estão relacionados à implementação incorreta de onMeasure e onDraw: substituição excessiva, chamada desnecessária de requestLayout(), criação de objetos em onDraw.

onMeasure: medindo dimensões da View

onMeasure — a fase mais importante e mais complexa do View Lifecycle. Nesta etapa, o Android determina quanto espaço a View ocupará na tela. O sistema passa MeasureSpec — instruções empacotadas em int que consistem em um modo e um tamanho.

Os três modos de MeasureSpec:

ModoConstanteSignificadoExemplo
EXACTLYMeasureSpec.EXACTLYTamanho exato definido pelo pai (match_parent ou largura fixa)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView pode ter até o tamanho máximo especificado (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDSem restrições — View pode ter qualquer tamanho (ScrollView, RecyclerView)largura ilimitada → MeasureSpec(0, UNSPECIFIED)

A implementação do onMeasure deve:

  • Chamar setMeasuredDimension(int width, int height) para salvar as dimensões medidas.
  • Considerar o padding — subtrair getPaddingLeft() + getPaddingRight() da largura disponível.
  • Para ViewGroup — medir todos os filhos via measureChild() ou measureChildWithMargins().
  • Para wrap_content — calcular o tamanho com base no conteúdo (texto, imagem).
  • Não chamar requestLayout() dentro de onMeasure — isso causará um loop infinito.

Erro típico: não considerar MeasureSpec ao usar wrap_content. Se uma View estiver definida como wrap_content, mas onMeasure não lidar com AT_MOST e retornar um tamanho fixo, a View será cortada ou ocupará mais espaço do que o necessário.

onLayout: posicionando Views na tela

onLayout — a fase em que uma View ou ViewGroup organiza seus filhos dentro de seus limites. Para uma View comum (não ViewGroup), onLayout não é necessário — o sistema chama layout() com os parâmetros passados pelo pai. Para uma ViewGroup, onLayout é obrigatório — sem ele, as Views filhas não serão posicionadas.

Assinatura do onLayout:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // organizando Views filhas
}

O parâmetro changed indica se a posição ou o tamanho da View mudou em comparação com o layout anterior. Se for false, a View pode pular o recálculo das posições dos filhos para otimização.

Para ViewGroup, onLayout deve:

  • Iterar por todos os filhos via getChildCount() e getChildAt(i).
  • Para cada filho, determinar left, top, right, bottom — coordenadas dentro da ViewGroup (considerando o padding).
  • Chamar child.layout(l, t, r, b) para cada filho.
  • Considerar gravity, margins, alignment.

onLayout é chamado após onMeasure — as dimensões medidas estão disponíveis via getMeasuredWidth()/getMeasuredHeight(). Se uma View filha tiver dimensões reais diferentes após layout(), requestLayout() será chamado para remedir. Isso é chamado de “passe de layout” e pode desencadear uma reação em cadeia de recálculos.

onDraw: desenhando conteúdo do Canvas

onDraw — a fase em que uma View se desenha no Canvas. Esta é a única fase que pode ser chamada várias vezes sem onMeasure e onLayout — se a View estiver marcada como invalidate(). O Canvas fornece API de desenho: drawLine, drawRect, drawCircle, drawText, drawBitmap e drawPath.

Regras do onDraw:

  • Não crie objetos em onDraw — cada chamada onDraw deve usar objetos pré-criados (Path, Paint, Rect). Criar objetos em onDraw causa pausas do GC e quadros perdidos.
  • Não chame requestLayout() ou invalidate() dentro de onDraw — isso desencadeará um loop infinito de redesenho.
  • Não faça cálculos longos — onDraw é executado na thread da UI. Cálculos complexos devem ser movidos para uma thread em segundo plano ou pré-calculados.
  • Use aceleração de hardware — desde a API 14+, o Canvas pode funcionar via GPU. Para gráficos complexos (gradientes, sombras, rotações), a aceleração HW fornece até 300% de melhoria de desempenho.
  • Desenhe apenas a área visível — use canvas.clipRect() para recortar partes invisíveis.

Ordem de desenho em ViewGroup: fundo (setBackgroundDrawable) → onDraw (conteúdo) → dispatchDraw (Views filhas) → onDrawForeground (primeiro plano). dispatchDraw chama onDraw de cada filho. Sobrescrever dispatchDraw é usado para aplicar efeitos sobre os elementos filhos.

De acordo com as estatísticas do Android Vitals, as causas mais comuns de quadros perdidos em onDraw são criar objetos dentro do método (48%), chamar decodeResource (22%) e operações complexas com Path sem cache (15%).

Invalidação: quando uma View é redesenhada

Invalidação — o mecanismo que aciona o redesenho da View. Chamar invalidate() marca a View como “suja” e agenda a chamada de onDraw no próximo ciclo de desenho. Chamar requestLayout() é uma operação mais “pesada”, acionando o ciclo completo: onMeasure → onLayout → onDraw.

MétodoO que fazQuando usar
invalidate()Aciona onDraw sem onMeasure/onLayoutApenas a aparência mudou (cor, texto, progresso)
invalidate(Rect)Redesenha apenas a área especificadaParte da View mudou — animação, seleção
postInvalidate()Chama invalidate de uma thread não-UIThread em segundo plano atualizou dados para desenho
requestLayout()Aciona onMeasure → onLayout → onDrawO tamanho do conteúdo mudou (texto, imagem)
forceLayout()Marca a View para remedição forçadaEstado interno mudou, o tamanho pode ter mudado

Animações e View Lifecycle: ViewPropertyAnimator e ValueAnimator chamam invalidate() em cada quadro de animação. ObjectAnimator chama um setter na View, que se o setter mudar o tamanho (largura/altura), automaticamente chama requestLayout(). Isso pode ser caro para ViewGroups complexas: cada requestLayout aciona a hierarquia completa até a view raiz.

Regra de otimização: invalidate() em vez de requestLayout() em todos os lugares onde apenas a aparência muda (cor, transparência, rotação sem alteração de tamanho). Use requestLayout apenas ao alterar tamanhos ou conteúdo que afete o tamanho.

Otimização de Views personalizadas: melhores práticas

Views personalizadas são uma ferramenta poderosa para criar UI única, mas exigem adesão estrita às regras de desempenho. Aqui estão as principais recomendações do Google para otimização do View Lifecycle.

  • Pré-calcule tudo que pode ser calculado — tamanhos, coordenadas, caminho, cores de gradiente. Em onDraw, apenas desenhe.
  • Armazene em cache os resultados de medição — se uma View tiver dimensões fixas, salve o MeasureSpec e retorne setMeasuredDimension sem cálculos adicionais.
  • Use ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — para manipulação de toque.
  • Minimize o número de Views na hierarquia — Views personalizadas que combinam vários elementos são sempre mais rápidas que uma ViewGroup com 3–5 Views aninhadas. O Google recomenda no máximo 10 Views aninhadas por tela.
  • Use ConstraintLayout para hierarquia plana — ele constrói uma única ViewGroup com desempenho próximo ao RelativeLayout, mas sem aninhamento.
  • Desative a camada de hardware após o redesenho — use setLayerType(LAYER_TYPE_HARDWARE) para Views com animações e setLayerType(LAYER_TYPE_NONE) após a conclusão.
  • Use invalidate() com Rect — redesenhe apenas a área alterada, não a View inteira.
  • Evite overdraw — use Profile GPU Rendering no Android Studio para identificar redesenho desnecessário. O overdraw médio para aplicativos do Google é 1.5x, o máximo recomendado é 2.5x.

Exemplos de código View em Kotlin

Exemplo 1: View personalizada — indicador de progresso

Um indicador de progresso circular simples com implementação correta de 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 de progresso circular: onMeasure retorna um tamanho quadrado com base em MeasureSpec, onSizeChanged lembra as dimensões, onDraw desenha o fundo e o arco de progresso. Invalidate é chamado quando o progresso muda — onMeasure/onLayout não são afetados. Paint é criado uma vez no construtor, não em onDraw.

Exemplo 2: ViewGroup — FlowLayout simples

Uma ViewGroup personalizada que organiza Views filhas em linhas (como 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 sobrescreve onMeasure: mede cada filho, quebra para uma nova linha quando a largura é excedida, calcula a altura total. onLayout posiciona os filhos por coordenadas considerando as quebras de linha. generateLayoutParams retorna MarginLayoutParams para suportar margem em Views filhas.

Exemplo 3: onDraw com cache de Path

Uma View personalizada desenha uma curva Bezier suave, pré-calculando o Path e armazenando-o em 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)
    }
}

Cache de Path: isPathDirty = true apenas quando as dimensões da View mudam ou refreshWave() é chamado. Em onDraw, o Path é recalculado apenas se estiver “ sujo”. Isso evita recalcular a curva Bezier a cada quadro de animação, economizando CPU.

Perguntas frequentes

Como o View Lifecycle é diferente do Activity Lifecycle?

View Lifecycle é um processo cíclico de desenho (onMeasure → onLayout → onDraw) independente da criação/destruição da Activity. View não tem onStart/onStop — ou está visível (anexada a uma janela) ou não. Activity Lifecycle gerencia o estado do componente do aplicativo, View Lifecycle gerencia o desenho da UI.

Qual o efeito de requestLayout() no desempenho?

requestLayout() aciona o ciclo completo onMeasure → onLayout → onDraw para toda a árvore de Views a partir da raiz. Se requestLayout() for chamado com frequência (por exemplo, a cada quadro de animação), causa jank e quadros perdidos. De acordo com o Google, um requestLayout leva em média 2–5 ms em uma ViewGroup de 10 elementos. Para animações, use invalidate().

Quando onAttachedToWindow é chamado?

onAttachedToWindow é chamado quando uma View é anexada a uma Window — torna-se parte da hierarquia visível. Neste momento, a View recebe aceleração HW e acesso aos recursos da Window (WindowManager, Display). onAttachedToWindow é o lugar certo para registrar listeners de animação e BroadcastReceivers que vivem enquanto a View está visível.

O que é overdraw e como reduzi-lo?

Overdraw é uma situação em que um pixel é desenhado várias vezes em um único quadro. Cada passagem extra desperdiça tempo de GPU. Métodos de redução: defina windowBackground no tema (não desenhe fundo no layout), use canvas.clipRect(), evite mesclar fundos aninhados, use ConstraintLayout em vez de LinearLayout aninhado. Android Studio → Profile GPU Rendering → Overdraw mostra um mapa de cores do overdraw (azul = 1x, vermelho = 3x+).

É necessário super.onDraw() em uma View personalizada?

Sim, se a View tiver um fundo. super.onDraw() desenha o fundo da View. Se sua View personalizada não tiver fundo ou você desenhar seu próprio fundo, super.onDraw() pode ser omitido — isso economiza uma passagem de desenho. Para ViewGroup, super.dispatchDraw() é obrigatório — desenha as Views filhas.

Resumo

  • View Lifecycle — três fases de desenho: onMeasure (dimensões), onLayout (posição), onDraw (renderização).
  • onMeasure processa MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) e chama setMeasuredDimension.
  • onLayout em ViewGroup posiciona Views filhas com coordenadas left/top/right/bottom.
  • onDraw renderiza conteúdo no Canvas — não crie objetos dentro deste método.
  • invalidate() aciona apenas onDraw, requestLayout() aciona o ciclo completo onMeasure → onLayout → onDraw.
  • Views personalizadas aceleram a UI em 15–40%, mas exigem implementação correta de onMeasure e cache de objetos em onDraw.
  • Para gráficos complexos, use aceleração de hardware e armazene Path/Bitmap em cache.

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.

Discutir o projeto

Leia também