Misurazioni intrinseche nei layout di Scrivi

Una delle regole di Compose è che devi misurare i figli una sola volta; se li misuri due volte, viene generata un'eccezione di runtime. Tuttavia, a volte hai bisogno di alcune informazioni sui figli prima di misurarli.

Intrinsics ti consente di eseguire query sui figli prima che vengano misurati.

Per un componibile, puoi richiedere il valore IntrinsicSize.Min o IntrinsicSize.Max:

  • Modifier.width(IntrinsicSize.Min) - Qual è la larghezza minima necessaria per visualizzare correttamente i contenuti?
  • Modifier.width(IntrinsicSize.Max) - Qual è la larghezza massima necessaria per visualizzare correttamente i contenuti?
  • Modifier.height(IntrinsicSize.Min) - Qual è l'altezza minima necessaria per visualizzare correttamente i contenuti?
  • Modifier.height(IntrinsicSize.Max) - Qual è l'altezza massima necessaria per visualizzare correttamente i contenuti?

Ad esempio, se chiedi la minIntrinsicHeight di un Text con vincoli di width infiniti in un layout personalizzato, viene restituita l'height del Text con il testo disegnato su una singola riga.

Intrinsics in azione

Puoi creare un componibile che visualizzi due testi sullo schermo separati da un divisore:

Due elementi di testo affiancati, con un divisore verticale tra di loro

Per farlo, utilizza un Row con due componibili Text che riempiono lo spazio disponibile e un Divider al centro. Il Divider deve essere alto quanto il Text più alto e deve essere sottile (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
        )
    }
}

Il Divider si espande a tutto lo schermo, ma questo non è il comportamento desiderato:

Due elementi di testo affiancati, con un divisore tra di loro, ma il divisore si estende verso il basso oltre la parte inferiore del testo

Questo accade perché Row misura ogni figlio individualmente e l'altezza di Text non può essere utilizzata per vincolare il Divider.

Per fare in modo che il Divider riempia lo spazio disponibile con un'altezza specificata, utilizza il modificatore height(IntrinsicSize.Min).

height(IntrinsicSize.Min) ridimensiona i figli in modo che siano alti quanto la loro altezza intrinseca minima. Poiché questo modificatore è ricorsivo, esegue una query sulla minIntrinsicHeight di Row e dei suoi figli.

L'applicazione di questo modificatore al codice fa sì che funzioni come previsto:

@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 l'anteprima:

Due elementi di testo affiancati, con un divisore verticale tra di loro

L'altezza di Row viene determinata nel seguente modo:

  • La minIntrinsicHeight del componibile Row è la minIntrinsicHeight massima dei suoi figli.
  • La minIntrinsicHeight dell'elemento Divider è 0, poiché non occupa spazio se non vengono forniti vincoli.
  • La minIntrinsicHeight di Text è quella del testo per una width specifica.
  • Di conseguenza, il vincolo height dell'elemento Row diventa la minIntrinsicHeight massima dei Text.
  • Il Divider espande quindi la sua height al vincolo height fornito da Row.

Intrinsics nei layout personalizzati

Quando crei un modificatore Layout o layout personalizzato, le misurazioni intrinseche vengono calcolate automaticamente in base alle approssimazioni. Pertanto, i calcoli potrebbero non essere corretti per tutti i layout. Queste API offrono opzioni per sostituire questi valori predefiniti.

Per specificare le misurazioni intrinseche del Layout, sostituisci minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, e maxIntrinsicHeight dell'interfaccia MeasurePolicy durante la creazione.

@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 {
                // Measure and layout here
                // ...
            }

            override fun IntrinsicMeasureScope.minIntrinsicWidth(
                measurables: List<IntrinsicMeasurable>,
                height: Int
            ): Int {
                // Logic here
                // ...
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

Quando crei il modificatore layout personalizzato, sostituisci i metodi correlati nell'interfaccia 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.
}