Como a maioria dos outros kits de ferramentas de interface, o Compose renderiza um frame por várias fases diferentes. Por exemplo, o sistema de visualização do Android tem três fases principais: medição, layout e exibição. O Compose é parecido, mas tem outra fase importante chamada composição no início.
A documentação do Compose descreve a composição em Trabalhando com o Compose e Estado e Jetpack Compose.
As três fases de um frame
O Compose tem três fases principais:
- Composição: qual interface será exibida. O Compose executa funções que podem ser compostas e cria uma descrição da interface.
- Layout: onde a interface será colocada. Esta fase consiste em duas etapas: medição e posicionamento. Os elementos do layout são medidos e colocados, assim como todos os elementos filhos, em coordenadas 2D para cada nó na árvore de layout.
- Exibição: como a IU será renderizada. Os elementos da interface são exibidos em uma tela, geralmente do dispositivo.

Geralmente, a ordem dessas fases é a mesma, permitindo que os dados fluam em uma única
direção da composição ao layout e à exibição para produzir um frame. Esse processo é conhecido
como fluxo de dados unidirecional. BoxWithConstraints
, LazyColumn
e LazyRow
são exceções importantes em que a composição dos elementos filhos depende da fase de layout dos pais.
Conceitualmente, cada uma dessas fases acontece para cada frame. No entanto, para otimizar o desempenho, o Compose evita repetir trabalhos que calculariam os mesmos resultados das mesmas entradas em todas essas fases. O Compose ignora a execução de uma função combinável se puder reutilizar um resultado anterior. A interface do Compose não recria o layout nem redesenha toda a árvore se isso não for necessário. O Compose executa apenas a quantidade mínima de trabalho necessária para atualizar a interface. Essa otimização é possível porque o Compose monitora as leituras de estado nas diferentes fases.
Entenda as fases
Esta seção descreve com mais detalhes como as três fases do Compose são executadas para elementos combináveis.
Composição
Na fase de composição, o tempo de execução do Compose executa funções combináveis e gera uma estrutura em árvore que representa a interface. Essa árvore de UI consiste em nós de layout que contêm todas as informações necessárias para as próximas fases, conforme mostrado no vídeo a seguir:
Figura 2. A árvore que representa a interface do usuário criada na fase de composição.
Uma subseção da árvore de código e de interface fica assim:

Nesses exemplos, cada função combinável no código é mapeada para um único nó de layout na árvore de UI. Em exemplos mais complexos, os combináveis podem conter lógica e fluxo de controle, além de produzir uma árvore diferente com base em estados diferentes.
Layout
Na fase de layout, o Compose usa a árvore de UI produzida na fase de composição como entrada. A coleção de nós de layout contém todas as informações necessárias para decidir o tamanho e a localização de cada nó no espaço 2D.
Figura 4. A medição e o posicionamento de cada nó de layout na árvore da interface durante a fase de layout.
Durante a fase de layout, a árvore é percorrida usando o seguinte algoritmo de três etapas:
- Medir filhos: um nó mede os filhos, se houver.
- Decidir o próprio tamanho: com base nessas medições, um nó decide o próprio tamanho.
- Posicionar filhos: cada nó filho é posicionado em relação à posição do nó.
Ao final dessa fase, cada nó de layout tem:
- Uma largura e uma altura atribuídas
- Uma coordenada x, y onde ele deve ser desenhado
Relembre a árvore de UI da seção anterior:
Para essa árvore, o algoritmo funciona da seguinte maneira:
- O
Row
mede os filhosImage
eColumn
. - O
Image
é medido. Ele não tem filhos, então decide o próprio tamanho e o informa de volta aoRow
. - Em seguida, o
Column
é medido. Ele mede primeiro os próprios filhos (dois elementos combináveisText
). - O primeiro
Text
é medido. Ele não tem filhos, então decide o próprio tamanho e o informa de volta aoColumn
.- O segundo
Text
é medido. Ele não tem filhos, então decide o próprio tamanho e o informa de volta aoColumn
.
- O segundo
- O
Column
usa as medições dos filhos para decidir o próprio tamanho. Ele usa a largura máxima do filho e a soma da altura dos filhos. - O
Column
coloca os filhos em relação a si mesmo, colocando-os um abaixo do outro verticalmente. - O
Row
usa as medições dos filhos para decidir o próprio tamanho. Ele usa a altura máxima do filho e a soma das larguras dos filhos. Em seguida, ele posiciona os filhos.
Cada nó foi visitado apenas uma vez. O tempo de execução do Compose exige apenas uma transmissão pela árvore de UI para medir e posicionar todos os nós, o que melhora o desempenho. Quando o número de nós na árvore aumenta, o tempo gasto para percorrer ela aumenta de maneira linear. Por outro lado, se cada nó for visitado várias vezes, o tempo de travessia aumentará exponencialmente.
Desenho
Na fase de desenho, a árvore é percorrida novamente de cima para baixo, e cada nó se desenha na tela por vez.
Figura 5. A fase de exibição mostra os pixels na tela.
Usando o exemplo anterior, o conteúdo da árvore é desenhado da seguinte maneira:
- O
Row
desenha qualquer conteúdo que possa ter, como uma cor de plano de fundo. - O
Image
se desenha. - O
Column
se desenha. - O primeiro e o segundo
Text
são desenhados, respectivamente.
Figura 6. Uma árvore de UI e a representação desenhada dela.
Leituras de estado
Quando você lê o value
de um snapshot state
durante uma das fases
listadas anteriormente, o Compose monitora automaticamente o que ele estava fazendo ao ler
o value
. Esse monitoramento permite que o Compose execute o leitor novamente quando o
value
do estado muda e é a base da observabilidade de estado do Compose.
Geralmente, um estado é criado usando o método mutableStateOf()
e pode ser acessado de duas
maneiras: acessando diretamente a propriedade value
ou
usando um delegado de propriedade do Kotlin. Para mais informações, consulte Estado dos
elementos que podem ser compostos. Neste guia, uma "leitura de estado" se refere a um desses métodos de acesso equivalentes.
// State read without property delegate. val paddingState: MutableState<Dp> = remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.padding(paddingState.value) )
// State read with property delegate. var padding: Dp by remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.padding(padding) )
As funções "getter" e "setter" são usadas no
delegado de propriedade (link em inglês)
para acessar e atualizar o value
do estado. Essas funções getter e
setter são invocadas apenas quando você referencia a propriedade como um valor,
e não quando ela é criada. Por isso, as duas maneiras descritas anteriormente são
equivalentes.
Cada bloco de código que pode ser executado novamente quando um estado lido é modificado é um
escopo de reinicialização. O Compose monitora as mudanças de estado value
e reinicia os escopos
em diferentes fases.
Leituras de estado em fases
Como já mencionado, há três fases principais no Compose e ele monitora qual estado é lido em cada uma delas. Isso permite que o Compose notifique apenas as fases específicas que precisam executar o trabalho para cada elemento afetado da interface.
As seções a seguir descrevem cada fase e o que acontece quando um valor de estado é lido nela.
Fase 1: composição
As leituras de estado em uma função @Composable
ou um bloco lambda afetam a composição
e, possivelmente, as próximas fases. Quando o value
do estado muda, o
recompositor programa novas execuções em todas as funções combináveis que leem o
value
do estado. O ambiente de execução poderá ignorar algumas ou todas as
funções que podem ser compostas se as entradas não tiverem mudado. Para mais informações, consulte Como ignorar caso as entradas
não tenham mudado.
Dependendo do resultado da composição, a interface do Compose executará as fases de layout e exibição. Essas fases serão ignoradas se o conteúdo permanecer o mesmo, e nem o tamanho, nem o layout serão modificados.
var padding by remember { mutableStateOf(8.dp) } Text( text = "Hello", // The `padding` state is read in the composition phase // when the modifier is constructed. // Changes in `padding` will invoke recomposition. modifier = Modifier.padding(padding) )
Fase 2: layout
A fase de layout consiste em duas etapas: medição e posicionamento. A
etapa de medição executa a lambda de medida transmitida ao elemento combinável Layout
, ao
método MeasureScope.measure
da interface LayoutModifier
, entre outros.
A etapa de posição executa o bloco de posicionamento da função layout
, o bloco
lambda de Modifier.offset { … }
e funções semelhantes.
As leituras de estado durante cada uma dessas etapas afetam o layout e, possivelmente, a
fase de exibição. Quando o value
do estado muda, a interface do Compose programa a fase
de layout. Ela também executa a fase de exibição quando o tamanho ou a posição do estado são modificados.
var offsetX by remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.offset { // The `offsetX` state is read in the placement step // of the layout phase when the offset is calculated. // Changes in `offsetX` restart the layout. IntOffset(offsetX.roundToPx(), 0) } )
Fase 3: exibição
As leituras de estado durante o código de exibição afetam a fase de exibição. Exemplos comuns
incluem os métodos Canvas()
, Modifier.drawBehind
e Modifier.drawWithContent
. Quando
o value
do estado muda, a interface do Compose executa apenas a fase de exibição.
var color by remember { mutableStateOf(Color.Red) } Canvas(modifier = modifier) { // The `color` state is read in the drawing phase // when the canvas is rendered. // Changes in `color` restart the drawing. drawRect(color) }
Otimizar leituras de estado
Como o Compose realiza o monitoramento das leituras de estado localizadas, é possível minimizar a quantidade de trabalho realizado na leitura de cada estado em uma fase adequada.
Considere este exemplo. Este exemplo tem uma Image()
que usa o
modificador de deslocamento para deslocar a posição final do layout, resultando em um efeito
paralaxe quando o usuário rola a tela.
Box { val listState = rememberLazyListState() Image( // ... // Non-optimal implementation! Modifier.offset( with(LocalDensity.current) { // State read of firstVisibleItemScrollOffset in composition (listState.firstVisibleItemScrollOffset / 2).toDp() } ) ) LazyColumn(state = listState) { // ... } }
Esse código funciona, mas tem um desempenho fraco. Como já mencionado, o código
lê o value
do estado firstVisibleItemScrollOffset
e o transmite para
a função Modifier.offset(offset: Dp)
. Conforme o usuário rola a tela, o
value
do firstVisibleItemScrollOffset
muda. Como você aprendeu, o Compose
monitora todas as leituras de estado para poder reiniciar (invocar novamente) o código de leitura,
que, neste exemplo, é o conteúdo do Box
.
Esse é um exemplo de leitura de um estado na fase de composição. Isso não é necessariamente ruim, já que é, na verdade, a base da recomposição, permitindo que mudanças de dados emitam uma nova interface.
Ponto principal: este exemplo não é ideal porque todo evento de rolagem faz com que o conteúdo combinável seja reavaliado, medido, colocado no layout e, por fim, desenhado. Você aciona a fase do Compose em cada rolagem, mesmo que o conteúdo mostrado não tenha mudado, apenas a posição dele. É possível otimizar a leitura do estado para acionar novamente apenas a fase de layout.
Deslocamento com lambda
Há outra versão do modificador de deslocamento disponível:
Modifier.offset(offset: Density.() -> IntOffset)
.
Essa versão usa um parâmetro lambda em que o deslocamento resultante é retornado pelo bloco lambda. Atualize o código para usá-lo:
Box { val listState = rememberLazyListState() Image( // ... Modifier.offset { // State read of firstVisibleItemScrollOffset in Layout IntOffset(x = 0, y = listState.firstVisibleItemScrollOffset / 2) } ) LazyColumn(state = listState) { // ... } }
Por que o desempenho ficou melhor? O bloco lambda fornecido ao modificador é
invocado durante a fase de layout, especificamente durante a etapa de
posicionamento, ou seja, o estado firstVisibleItemScrollOffset
não é
mais lido durante a composição. Como o Compose monitora o momento de leitura do estado,
essa mudança significa que, se o value
do firstVisibleItemScrollOffset
for modificado,
o Compose precisará reiniciar apenas as fases de layout e exibição.
Obviamente, muitas vezes é necessário ler os estados na fase
de composição. Mesmo assim, há casos em que é possível minimizar o número de
recomposições ao filtrar as mudanças de estado. Para mais informações,
consulte derivedStateOf
: converter um ou vários objetos de estado em outro
estado.
Repetição de recomposição (dependência da fase cíclica)
Este guia mencionou anteriormente que as fases do Compose são sempre invocadas na mesma ordem e que não há como voltar no mesmo frame. No entanto, isso não impede que apps entrem em repetições de composição em frames diferentes. Veja este exemplo:
Box { var imageHeightPx by remember { mutableStateOf(0) } Image( painter = painterResource(R.drawable.rectangle), contentDescription = "I'm above the text", modifier = Modifier .fillMaxWidth() .onSizeChanged { size -> // Don't do this imageHeightPx = size.height } ) Text( text = "I'm below the image", modifier = Modifier.padding( top = with(LocalDensity.current) { imageHeightPx.toDp() } ) ) }
Este exemplo implementa uma coluna vertical, com a imagem na parte de cima e o texto abaixo dela. Ele usa Modifier.onSizeChanged()
para receber o tamanho resolvido
da imagem e, em seguida, usa Modifier.padding()
no texto para deslocá-lo para baixo.
A conversão não natural de Px
para Dp
já indica que o código
tem um problema.
O problema com esse exemplo é que o código não chega ao layout "final" em um único frame. O código depende de vários frames, executando trabalhos desnecessários e resultando em saltos da interface para o usuário.
Composição do primeiro frame
Durante a fase de composição do primeiro frame, imageHeightPx
é inicialmente
0
. Consequentemente, o código fornece o texto com Modifier.padding(top = 0)
.
A fase de layout subsequente invoca o callback do modificador onSizeChanged
,
que atualiza imageHeightPx
para a altura real da imagem. O Compose então
programa uma recomposição para o próximo frame. No entanto, durante a fase de
exibição atual, o texto é renderizado com um padding de 0
, já que o valor
imageHeightPx
atualizado ainda não foi refletido.
Composição do segundo frame
O Compose inicia o segundo frame, acionado pela mudança no valor de imageHeightPx
. Na fase de composição deste frame, o estado é lido no bloco de conteúdo Box
. O texto agora tem um padding que corresponde à altura da imagem. Durante a fase de layout, imageHeightPx
é definido novamente. No entanto,
nenhuma outra recomposição é programada porque o valor permanece consistente.
O exemplo pode parecer complicado, mas tome cuidado com este padrão geral:
Modifier.onSizeChanged()
,onGloballyPositioned()
ou algumas outras operações de layout- Atualização de alguns estados
- Use esse estado como entrada para um modificador de layout (
padding()
,height()
ou semelhante) - Possível repetição
Para corrigir o exemplo anterior, use os primitivos de layout adequados. O
exemplo anterior pode ser implementado com uma Column()
, mas talvez você tenha um exemplo mais
complexo que exija algo personalizado, o que vai exigir a criação de um
layout personalizado. Consulte o guia Layouts personalizados para mais informações.
O princípio geral é a presença de uma única fonte de verdade para vários elementos da interface que precisam ser medidos e posicionados entre si. O uso de um primitivo de layout adequado ou a criação de um layout personalizado significa que o elemento pai compartilhado mínimo serve como a fonte de verdade que pode coordenar a relação entre vários elementos. A introdução de um estado dinâmico viola esse princípio.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Estado e Jetpack Compose
- Listas e grades
- Kotlin para Jetpack Compose