Compose レイアウトの固有の測定値

Compose のルールのひとつとして、子を 1 回しか測定できないことが挙げられます。子を 2 回測定した場合、ランタイム例外がスローされます。ただし、測定する前に子の情報が必要になる場合もあります。

Intrinsic を使用すると、実際に測定する前に子をクエリできます。

コンポーザブルに対して、次のように IntrinsicSize.Min または IntrinsicSize.Max を要求できます。

  • Modifier.width(IntrinsicSize.Min) - コンテンツを正しく表示するために必要な最小の幅はいくつですか?
  • Modifier.width(IntrinsicSize.Max) - コンテンツを正しく表示するために必要な最大の幅はいくつですか?
  • Modifier.height(IntrinsicSize.Min) - コンテンツを正しく表示するために必要な最小の高さはいくつですか?
  • Modifier.height(IntrinsicSize.Max) - コンテンツを正しく表示するために必要な最大の高さはいくつでしょうか?

たとえば、カスタム レイアウトで width 制約が無限大の TextminIntrinsicHeight を要求すると、テキストが 1 行で描画された Textheight が返されます。

Intrinsic の使い方

次のように、2 つのテキストを分割線で区切って画面上に表示するコンポーザブルを作成できます。

2 つのテキスト要素を横に並べ、その間に縦方向の分割線を配置します

これを行うには、使用可能なスペースを埋める 2 つの Text コンポーザブルと中央の Divider を含む Row を使用します。Divider の高さは最も高い Text と同じにして、幅は狭くします(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
        )
    }
}

Divider が画面全体に拡大されますが、これは望ましい動作ではありません。

2 つのテキスト要素が横に並び、分割線で区切られていますが、分割線がテキストの下まで伸びています

これは、Row がそれぞれの子を個別に測定し、Text の高さを Divider の制約に使用できないためです。

代わりに Divider が指定の高さで空きスペースを埋めるようにするには、height(IntrinsicSize.Min) 修飾子を使用します。

height(IntrinsicSize.Min) は、子の高さが Intrinsic の最小の高さと同じになるようにサイズ設定します。この修飾子は再帰的であるため、Row とその子の minIntrinsicHeight をクエリします。

この修飾子をコードに適用すると、想定どおりに動作します。

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

プレビュー:

2 つのテキスト要素を横に並べ、その間に縦方向の分割線を配置します

Row の高さは次のように決定されます。

  • Row コンポーザブルの minIntrinsicHeight は、子の最大 minIntrinsicHeight です。
  • Divider 要素の minIntrinsicHeight は、制約が設定されていない場合はスペースを占有しないため、0 になります。
  • Text minIntrinsicHeight は、特定の width のテキストのものです。
  • したがって、Row 要素の height 制約が、Text の最大 minIntrinsicHeight になります。
  • Divider は自身の height を、Row で指定された height 制約まで拡大します。

カスタム レイアウトでの Intrinsic

カスタムの Layout 修飾子または layout 修飾子を作成すると、近似値に基づいて固有の測定値が自動的に計算されます。このため、すべてのレイアウトで計算が不正確になる場合があります。これらの API には、こうしたデフォルト値をオーバーライドするオプションが用意されています。

カスタム Layout の組み込み測定値を指定するには、作成時に MeasurePolicy インターフェースの minIntrinsicWidthminIntrinsicHeightmaxIntrinsicWidthmaxIntrinsicHeight をオーバーライドします。

@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.
        }
    )
}

カスタムの layout 修飾子を作成する場合は、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.
}