Jetpack Compose facilita mucho el diseño y la compilación de la IU de tu app. En este documento, se explican algunos de los componentes fundamentales que proporciona Compose para ayudarte a diseñar los elementos de tu IU, y se muestra cómo compilar diseños más especializados cuando los necesites.
Objetivos de los diseños en Compose
La implementación de Jetpack Compose del sistema de diseño tiene dos objetivos principales: ser de alto rendimiento y facilitar la escritura de diseños personalizados. Para lograr un alto rendimiento en Compose, se prohíbe que los elementos secundarios de diseño se midan más de una vez. En caso de que se necesiten varias mediciones, Compose tiene un sistema especial, las medidas intrínsecas. Puedes obtener más información sobre esta función en la sección sobre mediciones intrínsecas.
Conceptos básicos de las funciones que admiten composición
Las funciones que admiten composición son los componentes fundamentales de Compose. Una función de este tipo emite una Unit
que describe alguna parte de tu IU. La función toma alguna entrada y genera lo que se muestra en la pantalla. Para obtener más información sobre elementos componibles, consulta la documentación sobre el modelo mental de Compose.
Una función que admite composición podría emitir varios elementos de la IU. Sin embargo, si no indicas cómo deben organizarse, es posible que Compose lo haga de una forma que no te agrade. Por ejemplo, este código genera dos elementos de texto:
@Composable
fun ArtistCard() {
Text("Alfred Sisley")
Text("3 minutes ago")
}
Si no tiene indicaciones sobre cómo quieres organizarlos, Compose los apila uno encima del otro y resultan ilegibles:
Compose proporciona una colección de diseños listos para usar que te ayudan a organizar los elementos de la IU y facilitan la definición de tus propios diseños más especializados.
Componentes de diseño estándar
En muchos casos, puedes usar los elementos de diseño estándar de Compose.
Usa Column
para colocar elementos en sentido vertical en la pantalla.
@Composable
fun ArtistCard() {
Column {
Text("Alfred Sisley")
Text("3 minutes ago")
}
}
Del mismo modo, usa Row
para colocar los elementos en sentido horizontal en la pantalla. Tanto Column
como Row
admiten la configuración de alineación de los elementos que contienen.
@Composable
fun ArtistCard(artist: Artist) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(/*...*/)
Column {
Text(artist.name)
Text(artist.lastSeenOnline)
}
}
}
Usa Box
para colocar un elemento sobre otro.
Estos componentes fundamentales suelen ser todo lo que necesitas. Puedes escribir tu propia función que admita composición para combinar esos diseños en uno más elaborado que se adapte a tu app.
Para establecer la posición de los elementos secundarios dentro de un Row
, configura los argumentos horizontalArrangement
y verticalAlignment
. Para un objeto Column
, configura los argumentos verticalArrangement
y horizontalAlignment
:
@Composable
fun ArtistCard(artist: Artist) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Image(/*...*/)
Column { /*...*/ }
}
}
Modificadores
Los modificadores te permiten decorar o aumentar un elemento componible. Por ejemplo, puedes hacer todo esto:
- Cambiar el tamaño, el diseño, el comportamiento y el aspecto del elemento componible
- Agregar información (p. ej., etiquetas de accesibilidad)
- Procesar entradas del usuario
- Agregar interacciones de nivel superior, (p. ej., hacer que un elemento sea apto para hacer clic, desplazable, arrastrable o ampliable)
Los modificadores son objetos estándar de Kotlin. Para crear uno, llama a una de las funciones de clase Modifier
. Puedes encadenar esas funciones para crear una composición:
@Composable
fun ArtistCard(
artist: Artist,
onClick: () -> Unit
) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
Spacer(Modifier.size(padding))
Card(elevation = 4.dp) { /*...*/ }
}
}
En el código anterior, observa distintas funciones de modificadores que se usan juntas.
clickable
hace que un elemento componible reaccione a la entrada del usuario y muestre una onda.padding
coloca espacio alrededor de un elemento.fillMaxWidth
hace que el elemento componible ocupe el ancho máximo que le otorga su elemento superior.size()
especifica el ancho y la altura preferidos de un elemento.
El orden de los modificadores es importante
El orden de las funciones de los modificadores es importante. Como cada función realiza cambios en el Modifier
que muestra la función anterior, la secuencia afecta al resultado final. Veamos un ejemplo:
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
// rest of the implementation
}
}
En el código anterior, se puede hacer clic en toda el área, incluso en el padding que la rodea, porque se aplicó el modificador padding
después del modificador clickable
. Si se invierte el orden de los modificadores, el espacio que agrega padding
no reacciona a la entrada del usuario:
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.padding(padding)
.clickable(onClick = onClick)
.fillMaxWidth()
) {
// rest of the implementation
}
}
Modificadores integrados
Jetpack Compose proporciona una lista de modificadores integrados para ayudarte a decorar o compilar un elemento componible. Ya se introdujeron algunos modificadores, como padding
, clickable
y fillMaxWidth
. A continuación, se incluye una lista de otros modificadores comunes:
size
De forma predeterminada, los diseños proporcionados en Compose unen sus objetos secundarios. Sin embargo, puedes establecer un tamaño con el modificador size
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(/*...*/)
Column { /*...*/ }
}
}
Ten en cuenta que el tamaño especificado podría no respetarse si no cumple con las restricciones provenientes del elemento superior del diseño. Si necesitas que el tamaño del elemento componible se corrija independientemente de las restricciones entrantes, usa el modificador requiredSize
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.requiredSize(150.dp)
)
Column { /*...*/ }
}
}
En este ejemplo, incluso con el height
superior establecido en 100.dp
, la altura de la Image
será 150.dp
, ya que el modificador requiredSize
tiene prioridad.
Si quieres que un diseño secundario rellene todo el alto disponible que permite el elemento superior, agrega el modificador fillMaxHeight
(Compose también proporciona fillMaxSize
y fillMaxWidth
):
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.fillMaxHeight()
)
Column { /*...*/ }
}
}
Si deseas agregar padding sobre un modelo de referencia de texto para alcanzar una distancia específica desde la parte superior del diseño al modelo de referencia, usa el modificador paddingFromBaseline
:
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(
text = artist.name,
modifier = Modifier.paddingFromBaseline(top = 50.dp)
)
Text(artist.lastSeenOnline)
}
}
}
Desplazamiento
Para posicionar un diseño relacionado con su posición original, agrega el modificador offset
y configura el desplazamiento en los ejes y y x. Las compensaciones pueden ser positivas y no positivas. La diferencia entre padding
y offset
es que agregar un offset
a un elemento componible no cambia sus mediciones:
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(artist.name)
Text(
text = artist.lastSeenOnline,
modifier = Modifier.offset(x = 4.dp)
)
}
}
}
El modificador offset
se aplica horizontalmente según la dirección del diseño.
En un contexto de izquierda a derecha, un offset
positivo cambia el elemento hacia la derecha, mientras que en un contexto de derecha a izquierda desplaza el elemento hacia la izquierda.
Si necesitas establecer un desplazamiento sin considerar la dirección del diseño, consulta el modificador absoluteOffset
, en el que un valor de desplazamiento positivo siempre cambia el elemento hacia la derecha.
Seguridad de tipo en Compose
En Compose, existen modificadores que solo funcionan cuando se aplican a elementos secundarios de determinados elementos que admiten composición. Por ejemplo, si deseas que un elemento secundario sea tan grande como el Box
superior sin afectar el tamaño de Box
, usa el modificador matchParentSize
.
Compose aplica de manera forzosa esta seguridad de tipo mediante alcances personalizados.
Por ejemplo, matchParentSize
solo está disponible en BoxScope
.
Por lo tanto, solo se puede usar cuando se usa el elemento secundario dentro de un Box
.
Los modificadores con alcance notifican al elemento superior sobre cierta información del elemento secundario que el superior debe conocer. Por lo general, también se los conoce como modificadores de datos del elemento superior. Sus aspectos internos difieren de los modificadores de uso general, pero, desde una perspectiva de uso, estas diferencias no resultan importantes.
matchParentSize en el cuadro
Como se mencionó con anterioridad, si deseas que un diseño secundario tenga el mismo tamaño que un Box
superior sin afectar el tamaño del Box
, usa el modificador matchParentSize
.
Ten en cuenta que matchParentSize
solo está disponible dentro del alcance de un Box
, lo que significa que solo se aplica a elementos secundarios directos de elementos componibles de Box
.
En el siguiente ejemplo, el Spacer
secundario toma su tamaño desde el elemento Box
superior, que, a su vez, toma su tamaño del elemento secundario más grande, ArtistCard
en este caso.
@Composable
fun MatchParentSizeComposable() {
Box {
Spacer(Modifier.matchParentSize().background(Color.LightGray))
ArtistCard()
}
}
Si se usara fillMaxSize
en lugar de matchParentSize
, Spacer
tomaría todo el espacio disponible para el elemento superior, lo que podría hacer que este se expanda y rellene todo el espacio disponible.
El grosor en Fila y Columna
Como viste en la sección anterior Padding y tamaño, el tamaño de un objeto componible se define de forma predeterminada según el contenido al que está unido. Puedes configurar el tamaño de un elemento componible para que sea flexible dentro de su elemento superior usando el modificador weight
que solo está disponible en RowScope
y ColumnScope
.
Tomemos un objeto Row
que contiene dos elementos componibles Box
.
El primer cuadro recibe el doble de weight
del segundo, por lo que recibe el doble del ancho. Dado que Row
tiene 210.dp
de ancho, el primer Box
tiene 140.dp
de ancho y, el segundo, 70.dp
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Image(
/*...*/
modifier = Modifier.weight(2f)
)
Column(
modifier = Modifier.weight(1f)
) {
/*...*/
}
}
}
Diseños desplazables
Obtén más información sobre los diseños desplazables en la documentación sobre gestos de Compose.
Para obtener información sobre listas y listas Lazy, consulta la documentación sobre listas de Compose.
Diseños receptivos
Un diseño debe tener en cuenta diferentes orientaciones de pantalla y tamaños de factores de forma. Compose ofrece configuraciones integradas de forma inmediata para facilitar la adaptación de tus diseños componibles a diferentes configuraciones de pantalla.
Restricciones
Para conocer las restricciones que provienen del elemento superior y diseñar el diseño según corresponda, puedes usar BoxWithConstraints
. Las restricciones de medición se pueden encontrar en el alcance de la lambda de contenido. Puedes usar estas restricciones de medición a fin de componer diferentes diseños para distintas configuraciones de pantalla:
@Composable
fun WithConstraintsComposable() {
BoxWithConstraints {
Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
}
}
Diseños basados en ranuras
Compose proporciona una gran variedad de elementos componibles basados en Material Design con la dependencia androidx.compose.material:material
(que se incluye cuando se crea un proyecto de Compose en Android Studio) para facilitar la compilación de IU. Estos elementos incluyen Drawer
, FloatingActionButton
y TopAppBar
.
Los componentes de Material usan mucho las API de ranuras, un patrón que introduce Compose para agregar una capa de personalización sobre elementos componibles. Este enfoque hace que los componentes sean más flexibles, ya que aceptan un elemento secundario que puede configurarse automáticamente, en lugar de tener que exponer cada parámetro de configuración del elemento secundario.
Las ranuras dejan un espacio vacío en la IU para que el desarrollador lo complete como quiera. Por ejemplo, estas son las ranuras que puedes personalizar en una TopAppBar
:
Los elementos componibles suelen adoptar una expresión lambda que admite composición content
(content: @Composable
() -> Unit
). Las API con ranuras exponen varios parámetros de content
para usos específicos.
Por ejemplo, TopAppBar
te permite proporcionar el contenido para title
, navigationIcon
y actions
.
Por ejemplo, Scaffold
te permite implementar una IU con la estructura básica de diseño de Material Design. Scaffold
proporciona ranuras para los componentes de Material de nivel superior más comunes, como TopAppBar
, BottomAppBar
, FloatingActionButton
y Drawer
. Si usas Scaffold
, es fácil asegurarte de que esos componentes estén bien posicionados y funcionen de forma correcta.
@Composable
fun HomeScreen(/*...*/) {
Scaffold(
drawerContent = { /*...*/ },
topBar = { /*...*/ },
content = { /*...*/ }
)
}
ConstraintLayout
ConstraintLayout
puede ayudar a posicionar elementos componibles en relación con otros en la pantalla y es una alternativa al uso de varios Row
, Column
, Box
anidados y diseños personalizados. ConstraintLayout
resulta útil cuando se implementan diseños más grandes con requisitos de alineación más complejos.
Para usar ConstraintLayout
en Compose, debes agregar esta dependencia en tu build.gradle
:
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha08"
En Compose, ConstraintLayout
funciona con una DSL:
- Las referencias se crean con
createRefs()
ocreateRefFor()
, y cada elemento componible enConstraintLayout
debe tener una referencia asociada. - Las restricciones se proporcionan mediante el modificador
constrainAs()
, que toma la referencia como parámetro y te permite especificar sus restricciones en la expresión lambda del cuerpo. - Las restricciones se especifican mediante
linkTo()
o algún otro método útil. parent
es una referencia existente que se puede usar para especificar restricciones hacia el mismo elementoConstraintLayout
.
En este ejemplo vemos uno de esos elementos que usa un ConstraintLayout
:
@Composable
fun ConstraintLayoutContent() {
ConstraintLayout {
// Create references for the composables to constrain
val (button, text) = createRefs()
Button(
onClick = { /* Do something */ },
// Assign reference "button" to the Button composable
// and constrain it to the top of the ConstraintLayout
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text("Button")
}
// Assign reference "text" to the Text composable
// and constrain it to the bottom of the Button composable
Text("Text", Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
})
}
}
Este código restringe la parte superior del Button
al elemento superior, con un margen de 16.dp
, y un Text
a la parte inferior del Button
, también con un margen de 16.dp
.
A fin de obtener más ejemplos para trabajar con ConstraintLayout
, prueba el codelab de diseños.
API desacoplada
En el ejemplo de ConstraintLayout
, las restricciones se especifican de forma intercalada, con un modificador en el elemento que admite composición al que se aplican. Sin embargo, hay situaciones en las que es preferible desacoplar las restricciones de los diseños a los que se aplican. Por ejemplo, quizás querrías cambiar las restricciones en función de la configuración de la pantalla o agregar una animación entre dos conjuntos de restricciones.
En casos como esos, puedes usar ConstraintLayout
de otro modo:
- Pasa un
ConstraintSet
como parámetro aConstraintLayout
. - Asigna referencias creadas en el
ConstraintSet
a los elementos que admiten composición con el modificadorlayoutId
.
@Composable
fun DecoupledConstraintLayout() {
BoxWithConstraints {
val constraints = if (minWidth < 600.dp) {
decoupledConstraints(margin = 16.dp) // Portrait constraints
} else {
decoupledConstraints(margin = 32.dp) // Landscape constraints
}
ConstraintLayout(constraints) {
Button(
onClick = { /* Do something */ },
modifier = Modifier.layoutId("button")
) {
Text("Button")
}
Text("Text", Modifier.layoutId("text"))
}
}
}
private fun decoupledConstraints(margin: Dp): ConstraintSet {
return ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
constrain(button) {
top.linkTo(parent.top, margin = margin)
}
constrain(text) {
top.linkTo(button.bottom, margin)
}
}
}
Luego, cuando necesites cambiar las restricciones, simplemente puedes pasar un ConstraintSet
diferente.
Más información
Obtén más información sobre ConstraintLayout en Compose, en la sección de Diseño de restricciones correspondiente a los Diseños que se encuentran en el codelab de Jetpack Compose y consulta los ejemplos de Compose que usan ConstraintLayout
para ver las API en acción.
Diseños personalizados
En Compose, los elementos de la IU se representan con funciones que admiten composición y que emiten una porción de la IU cuando se invoca, que luego se agregan a un árbol de IU que se procesa en la pantalla. Cada elemento de la IU tiene un elemento superior y, posiblemente, varios secundarios. Cada elemento también está ubicado dentro de su elemento superior, especificado como una posición (x, y) y un tamaño, especificado como width
y height
.
Los elementos superiores definen las restricciones de sus elementos secundarios. Se solicita a un elemento que defina su tamaño dentro de esas restricciones. Las restricciones limitan los valores mínimos y máximos de width
y height
de un elemento. Si un elemento tiene elementos secundarios, puede medir cada uno de ellos para ayudar a determinar su tamaño. Una vez que un elemento determina e informa su propio tamaño, tiene la oportunidad de definir cómo colocar sus elementos secundarios en relación con ellos, como se describe en detalle en Cómo crear diseños personalizados.
La medición de un solo paso es ideal en términos de rendimiento y permite que Compose procese de manera eficiente los árboles detallados de la IU. Supongamos que un elemento midió dos veces a su elemento secundario, y el elemento secundario, a su vez, midió dos veces a su elemento secundario, y así sucesivamente. Un solo intento para implementar toda la IU requeriría muchísimo trabajo, lo que dificultaría lograr que tu app funcione bien. Sin embargo, hay momentos en los que realmente necesitas información adicional, más allá de lo que te pueda indicar una sola medición del elemento secundario. Existen enfoques que pueden resolver una situación como esta, que se analizan en la sección de mediciones intrínsecas.
El uso de alcances define cuándo puedes medir y ubicar tus elementos secundarios. Solo podrás medir un diseño mientras se realicen los pases de medición y diseño. Podrás ubicar un elemento secundario únicamente durante los pases de diseño y solo después de que se haya medido de antemano. Debido a los alcances de Compose, como MeasureScope y PlacementScope, esto se aplica de manera forzosa en el tiempo de compilación.
Cómo usar el modificador de diseño
Puedes usar el modificador layout
para modificar la forma en que se mide y se organiza un elemento. Layout
es una expresión lambda; sus parámetros incluyen el elemento componible que puedes medir, que se pasó como measurable
, y las restricciones correspondientes a ese elemento, que se pasaron como constraints
. Un modificador de diseño personalizado puede verse de la siguiente manera:
fun Modifier.customLayoutModifier(...) =
this.layout { measurable, constraints ->
...
})
Mostremos un Text
en la pantalla y controlemos la distancia desde la parte superior hasta la línea de base de la primera línea de texto. Esto es exactamente lo que hace el modificador paddingFromBaseline
. Lo estamos implementando aquí como un ejemplo.
Para ello, usa el modificador layout
, que permite colocar el elemento componible de forma manual en la pantalla. Este es el comportamiento deseado en el que el padding superior de Text
está configurado en 24.dp
:
Este es el código que genera ese espaciado:
fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
// Check the composable has a first baseline
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// Height of the composable with padding - first baseline
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
// Where the composable gets placed
placeable.placeRelative(0, placeableY)
}
}
Esto es lo que sucede en este código:
- En el parámetro lambda
measurable
, puedes medir elText
representado por el parámetro medible llamando ameasurable.measure(constraints)
. - A fin de especificar el tamaño del elemento componible, llama al método
layout(width, height)
, que también proporciona una expresión lambda que se usa para posicionar los elementos secundarios. En este caso, es la altura entre la última línea de base y el padding superior agregado. - Para posicionar los elementos unidos en la pantalla, llama a
placeable.place(x, y)
. Si no se colocan los elementos unidos, no serán visibles. La posicióny
corresponde al padding superior, es decir, la posición de la primera línea de base del texto.
Para verificar que funcione como se espera, usa este modificador sobre un Text
:
@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
}
}
@Preview
@Composable
fun TextWithNormalPaddingPreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.padding(top = 32.dp))
}
}
Cómo crear diseños personalizados
El modificador layout
solo cambia el elemento componible al que se llama. Para medir y diseñar varios elementos componibles, usa el elemento Layout
. Ese elemento te permite medir e implementar elementos secundarios de forma manual. Todos los diseños de nivel superior, como Column
y Row
, se compilan con el elemento componible Layout
.
Compilemos una versión muy básica de Column
. La mayoría de los diseños personalizados siguen este patrón:
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
children = content
) { measurables, constraints ->
// measure and position children given constraints logic here
}
}
Al igual que el modificador layout
, measurables
es la lista de elementos secundarios que deben medirse, y constraints
son las restricciones del elemento superior.
Siguiendo la misma lógica de antes, se puede implementar MyBasicColumn
de la siguiente manera:
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each children
measurable.measure(constraints)
}
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Track the y co-ord we have placed children up to
var yPosition = 0
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.placeRelative(x = 0, y = yPosition)
// Record the y co-ord placed up to
yPosition += placeable.height
}
}
}
}
Los elementos componibles secundarios están limitados por las restricciones Layout
(sin las restricciones minHeight
) y se colocan según la yPosition
del elemento componible anterior.
Así es cómo se usaría ese elemento personalizado:
@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
MyBasicColumn(modifier.padding(8.dp)) {
Text("MyBasicColumn")
Text("places items")
Text("vertically.")
Text("We've done it by hand!")
}
}
Diseños personalizados en acción
Obtén más información sobre los diseños y modificadores personalizados en el codelab de Diseños en Jetpack Compose y consulta los ejemplos de Compose que crean diseños personalizados para ver las API en acción.
Dirección del diseño
Para cambiar la dirección del diseño de un elemento componible, cambia el objeto de composition local LocalLayoutDirection
.
Si quieres posicionar elementos componibles de manera manual en la pantalla, la LayoutDirection
forma parte del LayoutScope
del modificador layout
o del elemento Layout
.
Cuando uses layoutDirection
, posiciona los elementos que admiten composición con place
. A diferencia del método placeRelative
, place
no cambia en función de la dirección del diseño (de izquierda a derecha o de derecha a izquierda).
Medidas intrínsecas
Una de las reglas de Compose es que solo debes medir tus elementos secundarios una vez. Si lo haces dos veces, se genera una excepción de tiempo de ejecución. Sin embargo, hay momentos en los que necesitas información sobre tus elementos secundarios antes de medirlos.
Los elementos intrínsecos te permiten realizar consultas a los elementos secundarios antes de que se midan realmente.
Para un elemento componible, puedes solicitar su intrinsicWidth
o intrinsicHeight
:
(min|max)IntrinsicWidth
: Con esta altura, ¿cuál es el ancho mínimo y máximo con el que puedes pintar el contenido de manera correcta?(min|max)IntrinsicHeight
: Con este ancho, ¿cuál es la altura mínima o máxima con la que puedes pintar correctamente el contenido?
Por ejemplo, si solicitas la minIntrinsicHeight
de un Text
con width
infinito, se mostrará la height
del Text
como si se hubiera dibujado el texto en una sola línea.
Funciones intrínsecas en acción
Imagina que queremos crear un elemento componible que muestre dos textos en la pantalla separados por un divisor como este:
¿Cómo podemos hacer esto? Podemos tener un objeto Row
con dos Text
que se expandan tanto como sea posible y un Divider
en el medio. Queremos que el divisor sea delgado (width = 1.dp
) y tan alto como el Text
más alto.
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.Start),
text = text1
)
Divider(
color = Color.Black,
modifier = Modifier.fillMaxHeight().width(1.dp)
)
Text(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
.wrapContentWidth(Alignment.End),
text = text2
)
}
}
@Preview
@Composable
fun TwoTextsPreview() {
MaterialTheme {
Surface {
TwoTexts(text1 = "Hi", text2 = "there")
}
}
}
En la vista previa, vemos que el divisor se expande a toda la pantalla, pero eso no es lo que deseamos:
Esto ocurre porque Row
mide cada elemento secundario de forma individual, y la altura de Text
no se puede usar para restringir Divider
. Queremos que el Divider
ocupe el espacio disponible con una altura determinada. Para eso, podemos usar el modificador height(IntrinsicSize.Min)
.
height(IntrinsicSize.Min)
ajusta su tamaño a los elementos secundarios para que sean tan altos como su altura mínima intrínseca. Como es recurrente, realizará consultas a Row
y sus elementos secundarios minIntrinsicHeight
.
Cuando lo apliquemos a nuestro código, funcionará según lo esperado:
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier.height(IntrinsicSize.Min)) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.Start),
text = text1
)
Divider(
color = Color.Black,
modifier = Modifier.fillMaxHeight().width(1.dp)
)
Text(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
.wrapContentWidth(Alignment.End),
text = text2
)
}
}
Con vista previa:
La minIntrinsicHeight
del elemento componible Row
será la minIntrinsicHeight
máxima de sus elementos secundarios. El minIntrinsicHeight
de Divider element's
es 0, ya que no ocupa espacio si no se le aplican restricciones. El minIntrinsicHeight
de Text
será el del texto según un width
específico. Por lo tanto, la restricción height
del elemento Row
será la minIntrinsicHeight
máxima de los Text
. Luego, Divider
expandirá su height
a la restricción height
proporcionada por la Row
.
Funciones intrínsecas en tus diseños personalizados
Cuando se crea un modificador Layout
o layout
personalizado, las mediciones intrínsecas se calculan automáticamente en función de aproximaciones. Por lo tanto, es posible que los cálculos no sean correctos para todos los diseños. Estas API ofrecen opciones para anular estos valores predeterminados.
A fin de especificar las mediciones intrínsecas de tu Layout
personalizado, anula los minIntrinsicWidth
, minIntrinsicHeight
, maxIntrinsicWidth
y maxIntrinsicHeight
de la interfaz MeasurePolicy
al momento de su creación.
@Composable
fun MyCustomComposable(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
return object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
) = {
// Logic here
}
// Other intrinsics related methods have a default value,
// you can override only the methods that you need.
}
}
Cuando crees el modificador layout
personalizado, anula los métodos relacionados en la interfaz LayoutModifier
.
fun Modifier.myCustomModifier(/* ... */) = this.then(object : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
): Int = {
// Logic here
}
// Other intrinsics related methods have a default value,
// you can override only the methods that you need.
})
Más información
Obtén más información sobre las mediciones intrínsecas en la sección Funciones intrínsecas del codelab de Diseños en Jetpack Compose.
Más información
Para obtener más información, prueba el codelab de diseños de Jetpack Compose.