Misurazioni intrinseche nei layout di Scrivi

Una delle regole di Compose è che i figli devono essere misurati una sola volta; misurare i figli due volte genera un'eccezione di runtime. Tuttavia, a volte hai bisogno di alcune informazioni sui tuoi figli prima di misurarli.

Gli intrinseci ti consentono di eseguire query sui bambini prima che vengano effettivamente misurati.

Per un componente componibile, puoi chiedere il IntrinsicSize.Min o il IntrinsicSize.Max:

  • Modifier.width(IntrinsicSize.Min) - Qual è la larghezza minima necessaria per visualizzare correttamente i tuoi 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 tuoi contenuti?

Ad esempio, se chiedi l'minIntrinsicHeight di un Text con vincoli di width infiniti in un layout personalizzato, verrà restituito l'height del Text con il testo disegnato su una sola riga.

Funzioni intrinseche in azione

Supponiamo di voler creare un composable che mostri due testi sullo schermo separati da un divisore come questo:

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

Come possiamo farlo? Possiamo avere un Row con due Text all'interno che si espandono il più possibile e un Divider al centro. Vogliamo che Divider sia alto come il Text più alto e 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
        )
    }
}

Se visualizziamo l'anteprima, vediamo che Divider si espande a tutto lo schermo e non è quello che vogliamo:

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

Ciò accade perché Row misura ogni bambino singolarmente e l'altezza di Text non può essere utilizzata per vincolare Divider. Vogliamo che il Divider riempia lo spazio disponibile con un'altezza specifica. A questo scopo, possiamo utilizzare il modificatore height(IntrinsicSize.Min) .

height(IntrinsicSize.Min) dimensiona i relativi elementi secondari in modo che siano alti almeno quanto la loro altezza intrinseca minima. Poiché è ricorsiva, eseguirà una query su Row e sui relativi elementi secondari minIntrinsicHeight.

Se applichiamo questa logica al nostro codice, funzionerà 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 anteprima:

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

L'minIntrinsicHeight del composable Row sarà il massimo minIntrinsicHeight dei suoi elementi secondari. L'minIntrinsicHeight dell'elemento Divider è 0 perché non occupa spazio se non vengono forniti vincoli; l'minIntrinsicHeight di Text sarà quello del testo dato un width specifico. Pertanto, il vincolo height dell'elemento Row sarà il massimo minIntrinsicHeight degli Text. Divider espanderà quindi il suo height fino al vincolo height fornito da Row.

Intrinseci 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 ignorare questi valori predefiniti.

Per specificare le misurazioni intrinseche del tuo Layout personalizzato, esegui l'override di 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, esegui l'override dei 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.
}