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 IntrinsicSize.Min o IntrinsicSize.Max:
Modifier.width(IntrinsicSize.Min): ¿Cuál es el ancho mínimo que necesitas para mostrar tu contenido correctamente?Modifier.width(IntrinsicSize.Max): ¿Cuál es el ancho máximo que necesitas para mostrar tu contenido correctamente?Modifier.height(IntrinsicSize.Min): ¿Cuál es la altura mínima que necesitas para mostrar tu contenido correctamente?Modifier.height(IntrinsicSize.Max): ¿Cuál es la altura máxima que necesitas para mostrar tu contenido correctamente?
Por ejemplo, si solicitas la minIntrinsicHeight de un Text con restricciones de width infinitas en un diseño personalizado, se mostrará la height del Text con el texto dibujado en una sola línea.
Funciones intrínsecas en acción
Puedes crear un elemento componible que muestre dos textos en la pantalla separados por un divisor:
Para ello, usa un Row con dos elementos Text componibles que llenen el espacio disponible y un Divider en el medio. El Divider debe ser tan alto como el Text más alto y delgado (width = 1.dp).
@Composable fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) { Row(modifier = modifier) { Text( modifier = Modifier .weight(1f) .padding(start = 4.dp) .wrapContentWidth(Alignment.Start), text = text1 ) VerticalDivider( color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp) ) Text( modifier = Modifier .weight(1f) .padding(end = 4.dp) .wrapContentWidth(Alignment.End), text = text2 ) } }
El Divider se expande a toda la pantalla, lo que no es el comportamiento deseado:
Esto ocurre porque Row mide cada elemento secundario de forma individual, y la altura de Text no se puede usar para restringir Divider.
Para que el Divider ocupe el espacio disponible con una altura determinada, usa el modificador height(IntrinsicSize.Min).
height(IntrinsicSize.Min) ajusta el tamaño de sus elementos secundarios para que sean tan altos como su altura intrínseca mínima. Como este modificador es recurrente, consulta el
minIntrinsicHeight de Row y sus elementos secundarios.
Si aplicas este modificador a tu código, funcionará según lo esperado:
@Composable fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) { Row(modifier = modifier.height(IntrinsicSize.Min)) { Text( modifier = Modifier .weight(1f) .padding(start = 4.dp) .wrapContentWidth(Alignment.Start), text = text1 ) VerticalDivider( 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") } } }
Con vista previa:
La altura de Row se determina de la siguiente manera:
- La
minIntrinsicHeightdel elemento componibleRowes laminIntrinsicHeightmáxima de sus elementos secundarios. - La
minIntrinsicHeightdel elementoDivideres 0, ya que no ocupa espacio si no se le aplican restricciones. - El
TextminIntrinsicHeightes el del texto para unwidthespecífico. - Por lo tanto, la restricción de
heightdel elementoRowse convierte en el valor máximo deminIntrinsicHeightde los elementosText. - Luego,
Dividerexpande suheighta la restricción deheightproporcionada porRow.
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 APIs ofrecen opciones para anular estos valores predeterminados.
Para especificar las mediciones intrínsecas de tu Layout personalizado, anula minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth y maxIntrinsicHeight de la interfaz MeasurePolicy cuando la creas.
Tu anulación debe coincidir con lo que hace measure. Por ejemplo, en una columna personalizada que apila sus elementos secundarios de forma vertical y le otorga a cada uno el ancho completo de la columna, la altura intrínseca mínima para un width determinado es la suma de las alturas intrínsecas mínimas de sus elementos secundarios para ese width:
@Composable fun MyCustomComposable( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Layout( content = content, modifier = modifier, measurePolicy = object : MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints ): MeasureResult { val childConstraints = constraints.copy(minHeight = 0) val placeables = measurables.map { measurable -> measurable.measure(childConstraints) } val width = constraints.constrainWidth( placeables.maxOfOrNull { it.width } ?: 0 ) val height = constraints.constrainHeight( placeables.sumOf { it.height } ) return layout(width, height) { var yPosition = 0 placeables.forEach { placeable -> placeable.placeRelative(x = 0, y = yPosition) yPosition += placeable.height } } } override fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int ): Int { return measurables.sumOf { measurable -> measurable.minIntrinsicHeight(width) } } // 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. El cálculo intrínseco debe coincidir con measure. Por ejemplo, para un height determinado, un modificador que agrega un padding fijo en todos los lados resta el padding vertical antes de consultar el minIntrinsicWidth del elemento secundario y agrega el padding horizontal al resultado:
fun Modifier.myCustomModifier(padding: Dp = 8.dp) = this then object : LayoutModifier { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = padding.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure( constraints.offset(-horizontal, -vertical) ) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ): Int { val paddingPx = padding.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val childHeight = if (height == Constraints.Infinity) { Constraints.Infinity } else { (height - vertical).coerceAtLeast(0) } return measurable.minIntrinsicWidth(childHeight) + horizontal } // Other intrinsics related methods have a default value, // you can override only the methods that you need. }
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Diseños personalizados {:#custom-layouts}
- Líneas de alineación en Jetpack Compose
- Fases de Jetpack Compose