Misurazioni intrinseche nei layout di Scrivi

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

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

A un composable puoi chiedere il intrinsicWidth o il intrinsicHeight:

  • (min|max)IntrinsicWidth: data questa larghezza, qual è la larghezza minima/massima con cui puoi dipingere correttamente i tuoi contenuti?
  • (min|max)IntrinsicHeight: data questa altezza, qual è l'altezza minima/massima con cui puoi dipingere correttamente i tuoi contenuti?

Ad esempio, se chiedi il minIntrinsicHeight di un Text con height infinito, verrà restituito il height del Text come se il testo fosse stato disegnato in una singola riga.

Funzioni intrinseche in azione

Immaginiamo di voler creare un composable che mostri sullo schermo due testi 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 al suo interno che si espandono il più possibile e un Divider al centro. Vogliamo che Divider sia alto quanto 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
        )
        HorizontalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

Se ne vediamo l'anteprima, notiamo che Divider si espande per l'intero schermo, ma non è ciò che vogliamo:

Due elementi di testo affiancati, con un divisore tra di loro, ma il divisore si estende fino alla parte inferiore del testo

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

height(IntrinsicSize.Min) assegna alle sue figlie tag di taglia che le costringono ad avere un'altezza pari alla loro altezza minima intrinseca. Poiché è ricorsiva, eseguirà una query su Row e sui suoi figli minIntrinsicHeight.

Se applichiamo questo 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
        )
        HorizontalDivider(
            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

Il minIntrinsicHeight del composable Row sarà il valore minIntrinsicHeight massimo dei suoi elementi secondari. Il valore minIntrinsicHeight dell'elemento Divider è 0 perché non occupa spazio se non vengono specificati vincoli. Il valore minIntrinsicHeight di Text sarà quello del testo a cui è stato assegnato un valore width specifico. Pertanto, la limitazione height dell'elemento Row sarà il valore minIntrinsicHeight massimo dei valori Text. Divider espanderà quindi il proprio height fino al vincolo height dato da Row.

Elementi intrinseci nei layout personalizzati

Quando crei un modificatore Layout o layout personalizzato, le misurazioni intrinseche vengono calcolate automaticamente in base ad approssimazioni. Pertanto, i calcoli potrebbero non essere corretti per tutti i layout. Queste API offrono opzioni per eseguire l'override di queste impostazioni predefinite.

Per specificare le misurazioni intrinseche del Layout personalizzato, override 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.
}