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 il minIntrinsicHeight di un Text con vincoli width infiniti in un layout personalizzato, viene restituito il height del Text con il testo disegnato su una sola riga.

Funzioni intrinseche in azione

Puoi creare un composable 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. 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, il che 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

Ciò accade perché Row misura ogni bambino singolarmente e l'altezza di Text non può essere utilizzata per vincolare Divider.

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

height(IntrinsicSize.Min) dimensiona i relativi elementi secondari in modo che siano alti quanto la loro altezza intrinseca minima. Poiché questo modificatore è ricorsivo, esegue query sul minIntrinsicHeight di Row e dei relativi elementi secondari.

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 anteprima:

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

L'altezza di Row è determinata come segue:

  • L'minIntrinsicHeight del composable Row è il valore massimo minIntrinsicHeight dei suoi elementi secondari.
  • Il valore minIntrinsicHeight dell'elemento Divider è 0, in quanto non occupa spazio se non vengono forniti vincoli.
  • Il Text minIntrinsicHeight è quello del testo per un width specifico.
  • Pertanto, il vincolo height dell'elemento Row diventa il valore massimo minIntrinsicHeight dei Text.
  • Il Divider espande quindi il suo height al vincolo height fornito dal 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.
}