Jetpack Compose を使用すると、アプリの UI の設計と構築が簡単になります。このドキュメントでは、UI 要素のレイアウトに役立つ Compose のビルディング ブロックについて説明し、必要に応じてより特化したレイアウトを作成する方法を紹介します。
Compose でのレイアウトの目標
Jetpack Compose でのレイアウト システムの実装には、パフォーマンスの向上と、カスタム レイアウトの容易な作成の 2 つの主な目的があります。Compose では、レイアウトの子を複数回測定するのを防ぐことで、高パフォーマンスを実現できます。複数回の測定が必要な場合、Compose には、固有の測定値という特別なシステムが用意されています。この機能について詳しくは、固有の測定値のセクションをご覧ください。
コンポーズ可能な関数の基本
コンポーズ可能な関数は、Compose の基本的なビルディング ブロックです。コンポーズ可能な関数とは、UI の一部を記述する Unit
を出力する関数のことです。この関数は入力を受け取り、画面に表示されるものを生成します。コンポーザブルの詳細については、Compose のメンタルモデルについてのドキュメントをご覧ください。
コンポーズ可能な関数は、複数の UI 要素を出力することがあります。ただし、配置方法のガイダンスが提供されないと、Compose は望ましくない方法で要素を配置する可能性があります。たとえば、次のコードは 2 つのテキスト要素を生成します。
@Composable
fun ArtistCard() {
Text("Alfred Sisley")
Text("3 minutes ago")
}
配置方法のガイダンスがないと、Compose はテキスト要素を重ねてしまい、読み取れなくなります。
Compose には、すぐに使用できるレイアウトのコレクションが用意されています。UI 要素の配置に役立ち、より特化した独自のレイアウトを簡単に定義できます。
標準レイアウト コンポーネント
多くの場合、Compose の標準レイアウト要素を使用するだけで済みます。
アイテムを画面上の垂直方向に配置するには、Column
を使用します。
@Composable
fun ArtistCard() {
Column {
Text("Alfred Sisley")
Text("3 minutes ago")
}
}
同様に、アイテムを画面上の水平方向に配置するには、Row
を使用します。Column
と Row
はどちらも、含まれる要素の配置の構成をサポートしています。
@Composable
fun ArtistCard(artist: Artist) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(/*...*/)
Column {
Text(artist.name)
Text(artist.lastSeenOnline)
}
}
}
ある要素を別の要素の上に配置するには、Box
を使用します。
ほとんどの場合、これらのビルディング ブロックだけで済みます。独自のコンポーズ可能な関数を作成することで、こうしたレイアウトを組み合わせ、アプリに適した、より手の込んだレイアウトにできます。
Row
内の子の位置を設定するには、horizontalArrangement
引数と verticalAlignment
引数を設定します。Column
の場合は、verticalArrangement
引数と horizontalAlignment
引数を設定します。
@Composable
fun ArtistCard(artist: Artist) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Image(/*...*/)
Column { /*...*/ }
}
}
修飾子
修飾子を使用すると、コンポーザブルを装飾または拡張できます。修飾子では、次のようなことができます。
- コンポーザブルのサイズ、レイアウト、動作、外観を変更する
- ユーザー補助ラベルなどの情報を追加する
- ユーザー入力を処理する
- 要素をクリック可能、スクロール可能、ドラッグ可能、ズーム可能にするなど、高レベルの操作を追加する
修飾子は標準の Kotlin オブジェクトです。Modifier
クラス関数のいずれかを呼び出して修飾子を作成します。こうした関数を連鎖させてコンポーズできます。
@Composable
fun ArtistCard(
artist: Artist,
onClick: () -> Unit
) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
Spacer(Modifier.size(padding))
Card(elevation = 4.dp) { /*...*/ }
}
}
上記のコードでは、さまざまな修飾子関数を一緒に使用しています。
clickable
は、コンポーザブルをユーザー入力に反応させ、波紋を表示します。padding
は、要素の周囲にスペースを挿入します。fillMaxWidth
は、コンポーザブルを親から与えられた最大幅に合わせて調整します。size()
は、要素の優先的な幅と高さを指定します。
修飾子の順序の重要性
修飾子関数の順序は重要です。各関数は前の関数が返す Modifier
を変更するため、順序は最終結果に影響を与えます。次の例をご覧ください。
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
// rest of the implementation
}
}
上記のコードでは、padding
修飾子が clickable
修飾子の後に適用されるため、周囲のパディングを含むエリア全体がクリック可能となります。修飾子の順序が逆の場合、padding
で追加されたスペースはユーザー入力に反応しません。
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.padding(padding)
.clickable(onClick = onClick)
.fillMaxWidth()
) {
// rest of the implementation
}
}
組み込み修飾子
Jetpack Compose には、コンポーザブルの装飾や拡張に役立つ組み込み修飾子のリストが用意されています。padding
、clickable
、fillMaxWidth
などの修飾子がすでに導入されており、他にも、次のリストに示すような一般的な修飾子があります。
size
Compose で提供されるレイアウトは、デフォルトでは子をラップしていますが、size
修飾子を使用してサイズを設定できます。
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(/*...*/)
Column { /*...*/ }
}
}
指定したサイズがレイアウトの親の制約を満たさない場合、そのサイズが適用されないことがあります。親の制約に関係なくコンポーザブルのサイズを固定する必要がある場合は、requiredSize
修飾子を使用します。
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.requiredSize(150.dp)
)
Column { /*...*/ }
}
}
この例では、親 height
が 100.dp
に設定されていても、requiredSize
修飾子が優先されるため、Image
の高さは 150.dp
になっています。
親によって許可されているすべての高さを子レイアウトで埋めるには、fillMaxHeight
修飾子を追加します(Compose では、fillMaxSize
と fillMaxWidth
も提供されています)。
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.fillMaxHeight()
)
Column { /*...*/ }
}
}
テキストのベースラインの上にパディングを追加して、レイアウトの上部からベースラインまで一定の距離を空ける場合は、paddingFromBaseline
修飾子を使用します。
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(
text = artist.name,
modifier = Modifier.paddingFromBaseline(top = 50.dp)
)
Text(artist.lastSeenOnline)
}
}
}
オフセット
レイアウトを元の位置に対して相対的に配置するには、次のように offset
修飾子を追加し、x 軸と y 軸でオフセットを設定します。オフセットは、正の数でも負の数でもかまいません。padding
と offset
の違いは、offset
をコンポーザブルに追加しても測定値自体は変更されない点です。
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(artist.name)
Text(
text = artist.lastSeenOnline,
modifier = Modifier.offset(x = 4.dp)
)
}
}
}
offset
修飾子は、レイアウト方向に従って横方向に適用されます。正の offset
を設定する場合、左から右方向のコンテキストでは要素が右に移動し、右から左方向のコンテキストでは要素が左に移動します。レイアウト方向を考慮せずにオフセットを設定する必要がある場合は、absoluteOffset
修飾子をご覧ください。この修飾子を使用すると、正のオフセット値を設定した場合に要素が常に右へ移動します。
Compose での型の安全性
Compose には、特定のコンポーザブルの子に適用される場合にのみ動作する修飾子があります。たとえば、子を Box
サイズに影響を与えずに親の Box
と同じサイズにするには、matchParentSize
修飾子を使用します。
Compose では、カスタム スコープによってこの型の安全性が適用されます。たとえば、matchParentSize
は BoxScope
でのみ使用できます。そのため、この修飾子を使用できるのは Box
内で子が使用されている場合に限られます。
スコープ修飾子は、子に関して親が知っておくべき情報を親に通知します。これは、一般に親データ修飾子とも呼ばれます。その内部構造は汎用修飾子とは異なりますが、使用量の観点から見るとこれらの違いは重要ではありません。
Box の matchParentSize
前述のように、Box
サイズに影響を与えずに子レイアウトを親 Box
と同じサイズにする場合は、matchParentSize
修飾子を使用します。
matchParentSize
は Box
スコープ内でしか使用できず、Box
コンポーザブルの直接の子にのみ適用されます。
以下の例では、子 Spacer
は親 Box
からサイズを取得し、親はそのサイズを最も大きな子(この場合は ArtistCard
)から取得しています。
@Composable
fun MatchParentSizeComposable() {
Box {
Spacer(Modifier.matchParentSize().background(Color.LightGray))
ArtistCard()
}
}
matchParentSize
ではなく fillMaxSize
を使用した場合、Spacer
は親が使用できるすべてのスペースを使用します。この場合、親は使用可能なスペース全体に拡大します。
行と列の比
前のセクションのパディングとサイズで説明したように、デフォルトでは、コンポーザブルのサイズは自身がラップするコンテンツによって定義されます。RowScope
と ColumnScope
でのみ使用可能な weight
修飾子を使用すると、コンポーザブルのサイズを親の内部で柔軟に変動するように設定することもできます。
たとえば、2 つの Box
コンポーザブルが含まれる Row
があるとします。最初のボックスには、2 番目のボックスの 2 倍の weight
が指定されているため、幅も 2 倍になります。Row
の幅は 210.dp
なので、最初の Box
の幅は 140.dp
、2 番目のボックスの幅は 70.dp
になります。
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Image(
/*...*/
modifier = Modifier.weight(2f)
)
Column(
modifier = Modifier.weight(1f)
) {
/*...*/
}
}
}
スクロール可能なレイアウト
スクロール可能なレイアウトについて詳しくは、Compose の操作のドキュメントをご覧ください。
リストと遅延リストについては、Compose リストのドキュメントをご覧ください。
レスポンシブ レイアウト
レイアウトは、さまざまな画面の向きとフォーム ファクタのサイズを考慮して設計する必要があります。Compose には、コンポーザブルのレイアウトをさまざまな画面構成に簡単に適応させることができるように、すぐに使えるメカニズムがいくつか用意されています。
制約
親の制約を把握し、それに応じてレイアウトを設計するには、BoxWithConstraints
を使用します。測定の制約は、コンテンツ ラムダのスコープにあります。これらの測定の制約を使用して、画面構成ごとに異なるレイアウトをコンポーズできます。
@Composable
fun WithConstraintsComposable() {
BoxWithConstraints {
Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
}
}
スロットベースのレイアウト
Compose では、androidx.compose.material:material
の依存関係(Android Studio で Compose プロジェクトを作成するときに組み込まれる)によって、マテリアル デザインをベースにしたさまざまなコンポーザブルが提供されるため、簡単に UI を作成できます。Drawer
、FloatingActionButton
、TopAppBar
などの要素がすべて提供されます。
マテリアル コンポーネントはスロット API を多用します。これは、コンポーザブルの上にカスタマイズのレイヤを適用するために Compose で導入されたパターンです。このアプローチでは、自身を構成できる子要素をコンポーネントが受け入れるため、子のすべての構成パラメータを公開する必要がなく、より柔軟にコンポーネントを利用できます。スロットは UI に空のスペースを残し、デベロッパーが自由に使用できるようにします。たとえば、TopAppBar
でカスタマイズできるスロットは次のとおりです。
コンポーザブルは通常、content
コンポーザブル ラムダ(content: @Composable
() -> Unit
)を取ります。スロット API は、特定の用途のために複数の content
パラメータを公開します。たとえば、TopAppBar
を使用すると、title
、navigationIcon
、actions
のコンテンツを提供できます。
Scaffold
を使用すると、基本的なマテリアル デザインのレイアウト構造で UI を実装できます。Scaffold
には、TopAppBar
、BottomAppBar
、FloatingActionButton
、Drawer
など、最も一般的なトップレベルのマテリアル コンポーネント向けのスロットが用意されています。Scaffold
を使用すると、こうしたコンポーネントを適切に配置し、ともに正しく動作させることが簡単になります。
@Composable
fun HomeScreen(/*...*/) {
Scaffold(
drawerContent = { /*...*/ },
topBar = { /*...*/ },
content = { /*...*/ }
)
}
ConstraintLayout
ConstraintLayout
は、コンポーザブルを画面上の他の要素に対し相対的に配置するのに役立ちます。複数のネストされた Row
、Column
、Box
要素やカスタムのレイアウト要素を使用する代わりの手段です。ConstraintLayout
は、配置要件がより複雑な、大規模なレイアウトを実装する場合に便利です。
Compose で ConstraintLayout
を使用するには、build.gradle
に次の依存関係を追加する必要があります。
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha08"
Compose の ConstraintLayout
は DSL で機能します。
- 参照は
createRefs()
またはcreateRefFor()
を使用して作成されます。ConstraintLayout
内の各コンポーザブルは、関連付けられた参照を持つ必要があります。 - 制約は、参照をパラメータとして受け取る
constrainAs()
修飾子を使用して指定します。この修飾子により、本文のラムダで制約を指定できます。 - 制約は、
linkTo()
またはその他の便利なメソッドを使用して指定します。 parent
は、ConstraintLayout
コンポーザブル自体に対する制約を指定するために使用できる、既存の参照です。
ConstraintLayout
を使用したコンポーザブルの例を次に示します。
@Composable
fun ConstraintLayoutContent() {
ConstraintLayout {
// Create references for the composables to constrain
val (button, text) = createRefs()
Button(
onClick = { /* Do something */ },
// Assign reference "button" to the Button composable
// and constrain it to the top of the ConstraintLayout
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text("Button")
}
// Assign reference "text" to the Text composable
// and constrain it to the bottom of the Button composable
Text("Text", Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
})
}
}
このコードは、Button
の頂部を親に対しマージン 16.dp
に制約し、Text
も Button
の底部に対しマージン 16.dp
に制約します。
ConstraintLayout
のその他の使用例については、レイアウトの Codelab をご覧ください。
API の分離
ConstraintLayout
の例では、制約はインラインで指定され、適用対象のコンポーザブルで修飾子が付けられていました。しかし、適用対象のレイアウトから制約を分離した方がよい場合もあります。たとえば、画面構成に基づいて制約を変更する場合や、2 つの制約セットの間でアニメーション化を行う場合があります。
このような場合は、別の方法で ConstraintLayout
を使用できます。
ConstraintSet
を、ConstraintLayout
のパラメータとして渡します。ConstraintSet
で作成した参照を、layoutId
修飾子を使用してコンポーザブルに割り当てます。
@Composable
fun DecoupledConstraintLayout() {
BoxWithConstraints {
val constraints = if (minWidth < 600.dp) {
decoupledConstraints(margin = 16.dp) // Portrait constraints
} else {
decoupledConstraints(margin = 32.dp) // Landscape constraints
}
ConstraintLayout(constraints) {
Button(
onClick = { /* Do something */ },
modifier = Modifier.layoutId("button")
) {
Text("Button")
}
Text("Text", Modifier.layoutId("text"))
}
}
}
private fun decoupledConstraints(margin: Dp): ConstraintSet {
return ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
constrain(button) {
top.linkTo(parent.top, margin = margin)
}
constrain(text) {
top.linkTo(button.bottom, margin)
}
}
}
その後、制約を変更する必要がある場合は、別の ConstraintSet
を渡すだけで済みます。
詳細
Jetpack Compose でのレイアウトの Codelab にある Constraint Layout のセクションで、Compose の ConstraintLayout の詳細をご覧になり、ConstraintLayout
を使用する Compose サンプルで API の実際の動作をご確認ください。
カスタム レイアウト
Compose では、UI 要素は、呼び出されたときに UI の一部を出力するコンポーズ可能な関数によって表され、画面上にレンダリングされる UI ツリーに追加されます。各 UI 要素には親が 1 つあり、場合によっては多くの子があります。各要素は、(x, y) 位置として指定された親内の場所に、width
および height
で指定されたサイズで配置されます。
親は子要素の制約を定義します。要素は、これらの制約内で自身のサイズを定義するように要求されます。制約により、要素の width
と height
の最小値と最大値が制限されます。要素に子要素がある場合、親の要素は、自身のサイズを判断しやすくするために、子のそれぞれを測定できます。要素が自身のサイズを決定して報告すると、カスタム レイアウトの作成で詳しく説明されているように、子要素を自身に対し相対的に配置する方法を定義できるようになります。
シングルパス測定はパフォーマンスに優れており、Compose は深い UI ツリーを効率的に処理できます。要素が子を 2 回測定し、その子が自身の子のいずれかを 2 回測定した場合など、UI 全体をレイアウトしようとすると多くの作業が必要になるため、アプリのパフォーマンスを良好に保つことが難しくなります。しかし、子の測定 1 回でわかることに加えて、追加の情報が本当に必要な場合もあります。このような状況に効率的に対処できるアプローチについては、固有の測定値セクションで説明しています。
スコープの使用により、子の測定と配置を行えるタイミングが決まります。レイアウトは、測定パスとレイアウトパス中にのみ測定でき、子は、レイアウトパス中と事前に測定した後でのみ配置できます。この操作は、Compose スコープ(MeasureScope や PlacementScope など)によりコンパイル時に適用されます。
レイアウト修飾子の使用
layout
修飾子を使用して、要素の測定方法と配置方法を変更できます。Layout
はラムダです。パラメータには、測定可能な要素(measurable
として渡される)と、そのコンポーザブルの定義された制約(constraints
として渡される)が含まれます。カスタム レイアウト修飾子は次のようになります。
fun Modifier.customLayoutModifier(...) =
this.layout { measurable, constraints ->
...
})
画面に Text
を表示し、テキストの先頭行の頂部からベースラインまでの距離を制御してみましょう。これは、paddingFromBaseline
修飾子で行うこととまったく同じです。ここでは、これを例として実装します。そのためには、layout
修飾子を使用して、コンポーザブルを画面に手動で配置します。Text
の上パディングを 24.dp
に設定した場合の望ましい動作は次のとおりです。
この間隔を生成するコードを次に示します。
fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
// Check the composable has a first baseline
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// Height of the composable with padding - first baseline
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
// Where the composable gets placed
placeable.placeRelative(0, placeableY)
}
}
このコードの流れは次のとおりです。
measurable
ラムダ パラメータでmeasurable.measure(constraints)
を呼び出して、測定可能なパラメータで表されるText
を測定します。layout(width, height)
メソッドを呼び出して、コンポーザブルのサイズを指定します。これにより、ラップされた要素の配置に使用するラムダも提供されます。この場合、最後のベースラインと追加された上パディングの間の高さです。placeable.place(x, y)
を呼び出して、ラップされた要素を画面に配置します。配置されていないと、ラップされた要素は表示されません。y
位置は上パディング(テキストの最初のベースライン位置)に対応します。
これが期待どおりに機能することを確認するには、Text
で次の修飾子を使用します。
@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
}
}
@Preview
@Composable
fun TextWithNormalPaddingPreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.padding(top = 32.dp))
}
}
カスタム レイアウトの作成
layout
修飾子は、呼び出し元のコンポーザブルのみを変更します。複数のコンポーザブルを測定して配置するには、代わりに Layout
コンポーザブルを使用します。このコンポーザブルを使用することで、子を手動で測定して配置できます。Column
や Row
などの上位レベルのレイアウトはすべて、Layout
コンポーザブルで作成されます。
Column
の非常に基本的なバージョンを作成しましょう。ほとんどのカスタム レイアウトは、このパターンに従います。
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
children = content
) { measurables, constraints ->
// measure and position children given constraints logic here
}
}
layout
修飾子と同様に、measurables
は測定する必要がある子のリストです。constraints
は親からの制約です。前と同じロジックに従って、MyBasicColumn
は次のように実装できます。
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each children
measurable.measure(constraints)
}
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Track the y co-ord we have placed children up to
var yPosition = 0
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.placeRelative(x = 0, y = yPosition)
// Record the y co-ord placed up to
yPosition += placeable.height
}
}
}
}
子コンポーザブルは Layout
制約によって(minHeight
なしに)制約され、前のコンポーザブルの yPosition
に基づいて配置されます。
カスタム コンポーザブルの使用方法は次のとおりです。
@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
MyBasicColumn(modifier.padding(8.dp)) {
Text("MyBasicColumn")
Text("places items")
Text("vertically.")
Text("We've done it by hand!")
}
}
カスタム レイアウトの実例
Jetpack Compose でのレイアウトの Codelab でカスタム レイアウトと修飾子の詳細をご覧になり、カスタム レイアウトを作成する Compose サンプルで API の実際の動作をご確認ください。
レイアウト方向
コンポーザブルのレイアウト方向を変更するには、LocalLayoutDirection
コンポジション ローカルを変更します。
コンポーザブルを手動で画面に配置する場合、LayoutDirection
は、layout
修飾子または Layout
コンポーザブルの LayoutScope
の一部になります。
layoutDirection
を使用する場合は、place
を使用してコンポーザブルを配置します。placeRelative
メソッドと異なり、place
はレイアウト方向(左から右 / 右から左)に基づいて変更されることはありません。
固有の測定値
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 element's
の 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 MyCustomComposable(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
return 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
) = {
// 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.
})
詳細
固有の測定値について詳しくは、Jetpack Compose でのレイアウトの Codelab にある Intrinsic のセクションをご覧ください。
詳細
詳細については、Jetpack Compose でのレイアウトの Codelab をご覧ください。