Misurazioni intrinseche nei layout di Scrivi

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

Intrinsics ti consente di interrogare i bambini prima che vengano effettivamente misurati.

In un componibile, puoi richiederne intrinsicWidth o intrinsicHeight:

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

Ad esempio, se chiedi il valore minIntrinsicHeight di un Text con width infinito, restituirà il valore height di Text come se il testo fosse stato tracciato in una singola riga.

Funzioni intrinseche in azione

Immagina di voler creare un componibile che mostri due testi sullo schermo separati da un divisore come segue:

Due elementi di testo affiancati, separati da un divisore verticale

Come possiamo farlo? Possiamo avere un elemento Row con due Text all'interno che si espande il più possibile e un Divider al centro. Vogliamo che la Divider sia alta come la più alta Text 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
        )
        Divider(
            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 è ciò che vogliamo:

Due elementi di testo affiancati, separati da un divisore, ma quest'ultimo si estende sotto la parte inferiore del testo

Questo accade perché Row misura ogni asset secondario singolarmente e l'altezza di Text non può essere utilizzata per limitare il valore di Divider. Vogliamo che Divider riempia lo spazio disponibile con una determinata altezza. In questo caso, possiamo usare il modificatore height(IntrinsicSize.Min) .

height(IntrinsicSize.Min) taglia i suoi figli costretti a raggiungere l'altezza minima intrinseca. Poiché è ricorsiva, eseguirà una query su Row e sui relativi elementi secondari minIntrinsicHeight.

Applicarlo 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
        )
        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")
        }
    }
}

Con anteprima:

Due elementi di testo affiancati, separati da un divisore verticale

Il valore minIntrinsicHeight dell'elemento componibile Row sarà il minIntrinsicHeight massimo dei relativi elementi secondari. Il valore minIntrinsicHeight dell'elemento Divider è 0 in quanto non occupa spazio se non vengono assegnati vincoli. Il valore minIntrinsicHeight di Text sarà quello del testo di un valore width specifico. Di conseguenza, il vincolo height dell'elemento Row corrisponderà al valore massimo minIntrinsicHeight dei Text. Divider espanderà quindi la sua height al vincolo height specificato dal Row.

Funzionalità intrinseche nei layout personalizzati

Quando crei un modificatore Layout o layout personalizzato, le misurazioni intrinseche vengono calcolate automaticamente in base alle approssimazioni. Di conseguenza, i calcoli potrebbero non essere corretti per tutti i layout. Queste API offrono opzioni per eseguire l'override di 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, sostituisci i metodi correlati nell'interfaccia di 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.
}