Al igual que la mayoría de los kits de herramientas de la IU, Compose renderiza un fotograma a través de varias fases distintas. Por ejemplo, el sistema Android View tiene tres fases principales: medición, diseño y dibujo. Compose es muy similar, pero tiene una fase adicional importante que se denomina composición al comienzo.
La documentación de Compose describe la composición en Acerca de Compose y El estado y Jetpack Compose.
Las tres fases de un fotograma
Compose tiene tres fases principales:
- Composición: Indica qué IU se mostrará. Compose ejecuta funciones que admiten composición y crea una descripción de la IU.
- Diseño: Indica dónde se ubicará la IU. Esta fase consta de dos pasos: medición y posición. Los elementos de diseño se miden y se ubican a sí mismos y a cualquier elemento secundario en coordenadas 2D para cada nodo en el árbol de diseño.
- Dibujo: Indica cómo se renderiza. Los elementos de la IU se dibujan en un lienzo, por lo general, en la pantalla de un dispositivo.

El orden de estas fases suele ser el mismo, lo que permite que los datos fluyan en una dirección desde la composición hasta el diseño y el dibujo para producir un fotograma (que también se conoce como flujo de datos unidireccional). BoxWithConstraints
, LazyColumn
y LazyRow
son excepciones notables, en las que la composición de sus elementos secundarios depende de la fase de diseño del elemento principal.
Conceptualmente, cada una de estas fases se produce para cada fotograma. Sin embargo, para optimizar el rendimiento, Compose evita el trabajo repetitivo que calcularía los mismos resultados a partir de las mismas entradas en todas estas fases. Compose omite la ejecución de una función de componibilidad si puede volver a usar un resultado anterior, y la IU de Compose no vuelve a diseñar ni a dibujar el árbol completo si no es necesario. Compose realiza solo la cantidad mínima de trabajo que se necesita para actualizar la IU. Esta optimización es posible porque Compose realiza un seguimiento de las lecturas de estado en las diferentes fases.
Comprende las fases
En esta sección, se describe con más detalle cómo se ejecutan las tres fases de Compose para los elementos componibles.
Composición
En la fase de composición, el tiempo de ejecución de Compose ejecuta funciones de componibilidad y genera una estructura de árbol que representa tu IU. Este árbol de IU consta de nodos de diseño que contienen toda la información necesaria para las siguientes fases, como se muestra en el siguiente video:
Figura 2: Es el árbol que representa tu IU y que se crea en la fase de composición.
Una subsección del árbol de código y de IU se ve de la siguiente manera:

En estos ejemplos, cada función que admite composición en el código se asigna a un solo nodo de diseño en el árbol de IU. En ejemplos más complejos, los elementos componibles pueden contener lógica y flujo de control, y producir un árbol diferente según los distintos estados.
Diseño
En la fase de diseño, Compose usa el árbol de IU producido en la fase de composición como entrada. La colección de nodos de diseño contiene toda la información necesaria para decidir el tamaño y la ubicación de cada nodo en el espacio 2D.
Figura 4: Medición y posición de cada nodo de diseño en el árbol de IU durante la fase de diseño.
Durante la fase de diseño, el árbol se recorre con el siguiente algoritmo de tres pasos:
- Medir elementos secundarios: Un nodo mide sus elementos secundarios si existen.
- Decidir su propio tamaño: En función de estas mediciones, un nodo decide su propio tamaño.
- Colocar elementos secundarios: Cada nodo secundario se coloca en relación con la posición del nodo.
Al final de esta fase, cada nodo de diseño tiene lo siguiente:
- Un ancho y una altura asignados
- Una coordenada X, Y en la que se debe dibujar
Recuerda el árbol de IU de la sección anterior:
Para este árbol, el algoritmo funciona de la siguiente manera:
- El
Row
mide sus elementos secundarios,Image
yColumn
. - Se mide el
Image
. No tiene elementos secundarios, por lo que decide su propio tamaño y lo informa alRow
. - A continuación, se mide el
Column
. Primero mide sus propios elementos secundarios (dos elementosText
que admiten composición). - Se mide el primer
Text
. No tiene elementos secundarios, por lo que decide su propio tamaño y lo informa aColumn
.- Se mide el segundo
Text
. No tiene elementos secundarios, por lo que decide su propio tamaño y lo informa aColumn
.
- Se mide el segundo
- El nodo
Column
usa las mediciones de los elementos secundarios para decidir su propio tamaño. Usa el ancho máximo del elemento secundario y la suma de la altura de sus elementos secundarios. - El
Column
coloca sus elementos secundarios en relación con sí mismo, y los ubica uno debajo del otro de forma vertical. - El nodo
Row
usa las mediciones de los elementos secundarios para decidir su propio tamaño. Usa la altura máxima del elemento secundario y la suma de los anchos de sus elementos secundarios. Luego, coloca sus elementos secundarios.
Ten en cuenta que cada nodo se visitó solo una vez. El tiempo de ejecución de Compose solo requiere un paso por el árbol de IU para medir y colocar todos los nodos, lo que mejora el rendimiento. Cuando aumenta la cantidad de nodos en el árbol, el tiempo que se dedica a recorrerlo aumenta de forma lineal. En cambio, si se visita cada nodo varias veces, el tiempo de recorrido aumenta de forma exponencial.
Dibujo
En la fase de dibujo, el árbol se recorre de nuevo de arriba hacia abajo, y cada nodo se dibuja en la pantalla por turnos.
Figura 5: En la fase de dibujo, se dibujan los píxeles en la pantalla.
Con el ejemplo anterior, el contenido del árbol se dibuja de la siguiente manera:
- El
Row
dibuja cualquier contenido que pueda tener, como un color de fondo. - El
Image
se dibuja solo. - El
Column
se dibuja solo. - El primer y el segundo
Text
se dibujan, respectivamente.
Figura 6: Un árbol de IU y su representación dibujada
Lecturas de estado
Cuando lees el value
de un snapshot state
durante una de las fases mencionadas anteriormente, Compose realiza un seguimiento automático de lo que estaba haciendo cuando leyó el value
. Este seguimiento permite que Compose vuelva a ejecutar el lector cuando cambia el value
del estado y representa la base de la observabilidad del estado en Compose.
Por lo general, el estado se crea con mutableStateOf()
, y puedes acceder a él mediante una de estas dos maneras: de forma directa con la propiedad value
o, como alternativa, usando un delegado de propiedad de Kotlin. Puedes obtener más información al respecto en El estado en elementos que admiten composición. Para los fines de esta guía, una "lectura de estado" se refiere a cualquiera de esos métodos de acceso 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) )
Dentro del delegado de propiedad, se usan las funciones de los métodos "get" y "set" para acceder al value
del estado y actualizarlo. Las funciones de estos métodos solo se invocan cuando haces referencia a la propiedad como un valor, en lugar de cuando se crea, motivo por el cual los dos métodos que se describieron anteriormente son equivalentes.
Cada bloque de código que se puede volver a ejecutar cuando cambia un estado de lectura es un permiso de reinicio. Compose realiza un seguimiento de los cambios de estado value
y reinicia los permisos en diferentes fases.
Lecturas de estado por fases
Como se mencionó anteriormente, Compose tiene tres fases principales y realiza un seguimiento del estado que se lee en cada una de estas. De esta manera, Compose puede notificar solo las fases específicas que deben realizar trabajos para cada elemento afectado de la IU.
En las siguientes secciones, se describe cada fase y lo que sucede cuando se lee un valor de estado en una de estas.
Fase 1: Composición
Las lecturas de estado dentro de una función @Composable
o un bloque de lambda afectan a la composición y, posiblemente, a las fases posteriores. Cuando cambia el value
del estado, el recomposer programa que se vuelvan a ejecutar todas las funciones de componibilidad que leen el value
de ese estado. Ten en cuenta que el tiempo de ejecución puede decidir omitir algunas o todas las funciones que admiten composición si las entradas no cambiaron. Consulta Cómo omitir procesos si las entradas no cambiaron para obtener más información.
Según el resultado de la composición, la IU de Compose ejecuta las fases de diseño y dibujo. Es posible que omita estas fases si el contenido continúa siendo el mismo, y el tamaño y el diseño no cambiarán.
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: Diseño
La fase de diseño consta de dos pasos: medida y posición. En el paso de medición, se ejecuta la lambda de medición que se pasa al elemento Layout
que admite composición, el método MeasureScope.measure
de la interfaz LayoutModifier
, entre otros.
En el paso de posición, se ejecuta el bloque de posición de la función layout
, el bloque de lambda de Modifier.offset { … }
y funciones similares.
Las lecturas de estado durante cada uno de estos pasos afectan el diseño y, posiblemente, la fase de dibujo. Cuando cambia el value
del estado, la IU de Compose programa la fase de diseño. También ejecuta la fase de dibujo si cambió el tamaño o la posición.
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: Dibujo
Las lecturas de estado mientras se dibuja el código afectan la fase de dibujo. Entre algunos ejemplos comunes, se incluyen Canvas()
, Modifier.drawBehind
y Modifier.drawWithContent
. Cuando cambia el value
del estado, la IU de Compose solo ejecuta la fase de dibujo.
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) }
Cómo optimizar las lecturas de estado
Dado que Compose realiza un seguimiento localizado de las lecturas de estado, puedes minimizar la cantidad de trabajo que se realiza leyendo cada estado en una fase adecuada.
Ten en cuenta el siguiente ejemplo. En este ejemplo, se muestra un objeto Image()
que usa el modificador de desplazamiento para desplazar la posición final del diseño, lo que produce, como resultado, un efecto de paralaje a medida que el usuario se desplaza.
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) { // ... } }
Este código funciona, pero no brinda un rendimiento óptimo. Tal como se describe, el código lee el value
del estado firstVisibleItemScrollOffset
y lo pasa a la función Modifier.offset(offset: Dp)
. A medida que el usuario se desplaza, el value
del firstVisibleItemScrollOffset
cambiará. Como aprendiste, Compose realiza un seguimiento de cualquier lectura de estado para poder reiniciar (volver a invocar) el código de lectura, que, en este ejemplo, es el contenido de Box
.
Este es un ejemplo de cómo se lee un estado dentro de la fase de composición. No es algo malo en absoluto y, de hecho, representa la base de la recomposición, lo que permite que los cambios en los datos emitan una IU nueva.
Punto clave: Este ejemplo no es óptimo porque cada evento de desplazamiento produce que se vuelva a evaluar, medir, diseñar y, por último, dibujar todo el contenido que admite composición. Activas la fase de Compose en cada desplazamiento, aunque el contenido que se muestra no haya cambiado, solo su posición. Puedes optimizar la lectura de estado para que solo se vuelva a activar la fase de diseño.
Compensación con lambda
Puedes encontrar otra versión del modificador de desplazamiento: Modifier.offset(offset: Density.() -> IntOffset)
.
Esta versión toma un parámetro lambda, en el que el bloque de lambda muestra el desplazamiento resultante. Actualiza el código para usarlo:
Box { val listState = rememberLazyListState() Image( // ... Modifier.offset { // State read of firstVisibleItemScrollOffset in Layout IntOffset(x = 0, y = listState.firstVisibleItemScrollOffset / 2) } ) LazyColumn(state = listState) { // ... } }
Entonces, ¿por qué tiene un mejor rendimiento? El bloque de lambda que le brindas al modificador se invoca durante la fase de diseño (específicamente, durante el paso de posición de esta fase), lo que significa que el estado de firstVisibleItemScrollOffset
ya no se lee durante la composición. Como Compose realiza un seguimiento del estado de lectura, este cambio implica que, si se modifica el value
de firstVisibleItemScrollOffset
, Compose solo tiene que reiniciar las fases de diseño y dibujo.
Desde luego, con frecuencia, es absolutamente necesario leer los estados en la fase de composición. Aun así, existen casos en los que puedes minimizar la cantidad de recomposiciones si filtras los cambios de estado. Para obtener más información al respecto, consulta derivedStateOf
: Convierte uno o varios objetos de estado en otro estado.
Bucle de recomposición (dependencia de la fase cíclica)
En esta guía, se mencionó anteriormente que las fases de Compose siempre se invocan en el mismo orden y que no hay manera de retroceder mientras estamos en el mismo fotograma. Sin embargo, eso no prohíbe que las apps ingresen a bucles de composición en fotogramas diferentes. Observa este ejemplo:
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() } ) ) }
En este ejemplo, se implementa una columna vertical, con la imagen en la parte superior y el texto debajo. Usa Modifier.onSizeChanged()
para obtener el tamaño resuelto de la imagen y, luego, usa Modifier.padding()
en el texto para desplazarlo hacia abajo.
La conversión antinatural de Px
a Dp
ya indica que el código tiene algún problema.
El problema con este ejemplo es que el código no llega al diseño "final" dentro de un solo fotograma. El código se basa en un suceso de varios fotogramas, que realiza un trabajo innecesario y hace que la IU aparezca en la pantalla para el usuario.
Composición del primer fotograma
Durante la fase de composición del primer fotograma, imageHeightPx
es inicialmente 0
. Por lo tanto, el código proporciona el texto con Modifier.padding(top = 0)
.
La fase de diseño posterior invoca la devolución de llamada del modificador onSizeChanged
, que actualiza imageHeightPx
a la altura real de la imagen. Luego, Compose programa una recomposición para el siguiente fotograma. Sin embargo, durante la fase de dibujo actual, el texto se renderiza con un padding de 0
, ya que aún no se refleja el valor imageHeightPx
actualizado.
Composición del segundo fotograma
Compose inicia el segundo fotograma, que se activa por el cambio en el valor de imageHeightPx
. En la fase de composición de este fotograma, el estado se lee dentro del bloque de contenido Box
. Ahora, el texto se proporciona con un padding que coincide con precisión con la altura de la imagen. Durante la fase de diseño, se vuelve a establecer imageHeightPx
. Sin embargo, no se programa ninguna recomposición adicional porque el valor sigue siendo coherente.
Es posible que este ejemplo parezca forzado, pero ten cuidado con este patrón general:
Modifier.onSizeChanged()
,onGloballyPositioned()
u otras operaciones de diseño- Actualiza algún estado
- Usa ese estado como entrada para un modificador de diseño (
padding()
,height()
o similares) - Posiblemente tendrás que repetir el proceso
Para solucionar el problema del ejemplo anterior, usa las primitivas de diseño correctas. El ejemplo anterior se puede implementar con un objeto Column()
, pero es posible que tengas un ejemplo más complejo que necesite una solución personalizada, que requerirá escribir un diseño personalizado. Consulta la guía de Diseños personalizados para obtener más información.
El principio general aquí es tener una sola fuente de información para varios elementos de la IU que se deben medir y ubicar con respecto al otro. Usar un primitivo de diseño correcto o crear un diseño personalizado implica que el elemento superior compartido mínimo funcione como la fuente de confianza que puede coordinar la relación entre varios elementos. Introducir un estado dinámico no cumple con este principio.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- El estado y Jetpack Compose
- Listas y cuadrículas
- Kotlin para Jetpack Compose