Compose のルールのひとつとして、子を 1 回しか測定できないことが挙げられます。子を 2 回測定した場合、ランタイム例外がスローされます。ただし、測定する前に子の情報が必要になる場合もあります。
Intrinsic を使用すると、実際に測定する前に子をクエリできます。
コンポーザブルに対して、次のように intrinsicWidth
または intrinsicHeight
を要求できます。
(min|max)IntrinsicWidth
: ある高さの場合に、コンテンツを正しく描画できる最小 / 最大の幅はいくつでしょうか。(min|max)IntrinsicHeight
: ある幅の場合に、コンテンツを正しく描画できる最小 / 最大の高さはいくつでしょうか。
たとえば、Text
の minIntrinsicHeight
を無限大の width
で要求した場合、テキストが 1 行で描画されているかのように、Text
の height
が返されます。
Intrinsic の使い方
次のように、2 つのテキストを分割線で区切って画面上に表示するコンポーザブルを作成するとします。
これを実現するには、Row
内に 2 つの Text
を含めてテキストが可能な限り拡大するようにして、中央に Divider
を含めます。Divider の高さは最も高い Text
と同じにして、幅は狭くします(width = 1.dp
)。
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
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
)
}
}
@Preview
@Composable
fun TwoTextsPreview() {
MaterialTheme {
Surface {
TwoTexts(text1 = "Hi", text2 = "there")
}
}
}
これをプレビューすると、想定とは異なり、分割線が画面全体に拡大されます。
これは、Row
がそれぞれの子を個別に測定するため、Text
の高さを使用して Divider
を制約できないためです。Divider
が指定の高さで空きスペースを埋めるように設定する必要があります。そのためには、height(IntrinsicSize.Min)
修飾子を使用します。
height(IntrinsicSize.Min)
は、子の高さが Intrinsic の最小の高さと同じになるように強制します。この修飾子は再帰的であるため、Row
とその子の minIntrinsicHeight
をクエリします。
これを次のようにコードに適用すると、想定どおりに動作します。
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
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
)
}
}
プレビュー:
Row
コンポーザブルの minIntrinsicHeight
が、子の最大 minIntrinsicHeight
になります。Divider
要素の minIntrinsicHeight
は、制約が設定されていない場合はスペースを占有しないため、0 になります。Text
の minIntrinsicHeight
は、特定の width
が指定されたテキストの minIntrinsicHeight になります。したがって、Row
要素の height
制約が、Text
の最大 minIntrinsicHeight
になります。Divider
は自身の height
を、Row
で指定された height
制約まで拡大します。
カスタム レイアウトでの Intrinsic
カスタムの Layout
修飾子または layout
修飾子を作成すると、近似値に基づいて固有の測定値が自動的に計算されます。このため、すべてのレイアウトで計算が不正確になる場合があります。これらの API には、こうしたデフォルト値をオーバーライドするオプションが用意されています。
カスタム Layout
の組み込み測定値を指定するには、作成時に MeasurePolicy
インターフェースの minIntrinsicWidth
、minIntrinsicHeight
、maxIntrinsicWidth
、maxIntrinsicHeight
をオーバーライドします。
@Composable
fun MyCustomLayout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Layout(
modifier = modifier,
content = content,
measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int {
// Logic for calculating custom maxIntrinsicHeight 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.
})