【Jetpack Compose】レイアウトについてまとめる
初めに
今回は Jetpack Compose におけるレイアウトについてまとめてみたいと思います。
Flutter のレイアウトと似ている部分も多くありますが、Flutter の書き方と比較しつつ、 Jetpack Compose ではどのように実装するかみていきたいと思います。
記事の対象者
- Jetpack Compose 初学者
目的
今回の目的は、 Jetpack Compose のレイアウトの実装方法をまとめることです。
Flutter だとどのようになるかを比較しつつ、基本的なレイアウトについては実現できるくらいまでまとめていきたいと思います。
実装
今回は以下の項目についてまとめてみたいと思います。
- Column
- Row
- Box
- Spacer
- Layout
Column
まずは Column についてみていきます。
以下のような Column のサンプルを作って実行してみます。
@Composable
fun ColumnSample(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
) {
Text(text = "Item 1", fontSize = 30.sp)
Text(text = "Item 2", fontSize = 30.sp)
Text(text = "Item 3", fontSize = 30.sp)
}
}
実行結果は以下のようになります。
テキストの Column が画面の左上にまとまって表示されていることがわかります。
デフォルトではこのような表示になります。

verticalArrangement
次に verticalArrangement を変更してみます。
verticalArrangement は名前の通り、垂直方向のレイアウト を変更できます。
Jetpack Composeの場合の書き方、 Flutter の場合の書き方、見た目は以下の通りです。
![]() |
見た目 | ||
|---|---|---|---|
| Top | Arrangement.Top | MainAxisAlignment.start | ![]() |
| Center | Arrangement.Center | MainAxisAlignment.center | ![]() |
| Bottom | Arrangement.Bottom | MainAxisAlignment.end | ![]() |
| SpaceBetween | Arrangement.SpaceBetween | MainAxisAlignment.spaceBetween | ![]() |
| SpaceAround | Arrangement.SpaceAround | MainAxisAlignment.spaceAround | ![]() |
| SpaceEvenly | Arrangement.SpaceEvenly | MainAxisAlignment.spaceEvenly | ![]() |
| spacedBy | Arrangement.spacedBy(50.dp) | spacing: 50 | ![]() |
horizontalAlignment
次に horizontalAlignment を変更してみます。
horizontalAlignment は名前の通り、水平方向のレイアウト を変更できます。
それぞれの場合のレイアウトは以下の通りです。
![]() |
見た目 | ||
|---|---|---|---|
| Start | Alignment.Start | CrossAxisAlignment.start | ![]() |
| CenterHorizontally | Alignment.CenterHorizontally | CrossAxisAlignment.center | ![]() |
| End | Alignment.End | CrossAxisAlignment.end | ![]() |
基本的にプロパティの名前は Jetpack Compose と Flutter で同じであることが多いです。
verticalArrangement の spacedBy に関しても spacing を用いることで実現できるようになったので、同じような実装ができると言えそうです。
Row
次に Row についてみていきます。
以下のような Row のサンプルを作って実行してみます。
@Composable
fun RowSample(modifier: Modifier = Modifier) {
Row(
modifier = modifier,
) {
Text(text = "Item 1", fontSize = 25.sp)
Text(text = "Item 2", fontSize = 25.sp)
Text(text = "Item 3", fontSize = 25.sp)
}
}
実行結果は以下のようになります。
テキストの Row が画面の左上にまとまって横並びに表示されていることがわかります。
デフォルトではこのような表示になります。

horizontalArrangement
次に horizontalArrangement を変更してみます。
horizontalArrangement は名前の通り、水平方向のレイアウト を変更できます。
それぞれの場合のレイアウトは以下の通りです。
![]() |
見た目 | ||
|---|---|---|---|
| Start | Arrangement.Start | MainAxisAlignment.start | ![]() |
| Center | Arrangement.Center | MainAxisAlignment.center | ![]() |
| End | Arrangement.Bottom | MainAxisAlignment.end | ![]() |
| SpaceBetween | Arrangement.SpaceBetween | MainAxisAlignment.spaceBetween | ![]() |
| SpaceAround | Arrangement.SpaceAround | MainAxisAlignment.spaceAround | ![]() |
| SpaceEvenly | Arrangement.SpaceEvenly | MainAxisAlignment.spaceEvenly | ![]() |
| spacedBy | Arrangement.spacedBy(50.dp) | spacing: 50 | ![]() |
verticalAlignment
次に verticalAlignment を変更してみます。
verticalAlignment は、垂直方向のレイアウト を変更できます。
それぞれの場合のレイアウトは以下の通りです。
![]() |
見た目 | ||
|---|---|---|---|
| Top | Alignment.Top | CrossAxisAlignment.start | ![]() |
| CenterVertically | Alignment.CenterVertically | CrossAxisAlignment.center | ![]() |
| Bottom | Alignment.Bottom | CrossAxisAlignment.end | ![]() |
Flutter では mainAxisAlignment、 crossAxisAlignment と表記しますが、 Jetpack Compose では verticalAlignment、 horizontalArrangement のように表記します。
使用しているのが Column か Row かによってどちらがメインでどちらがクロスかを考えなくて良いので、書きやすいと感じました。
Box
次に Box についてみていきます。
以下のような Box のサンプルを実行してみます。
@Composable
fun BoxSample(modifier: Modifier = Modifier) {
Box(
modifier = modifier
) {
Image(
painter = painterResource(id = R.drawable.sample_image),
contentDescription = "Sample Image",
modifier = Modifier.width(300.dp).height(200.dp),
contentScale = ContentScale.Crop
)
Text(
text = "Overlay Text",
color = Color.Blue,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
}
実行結果は以下のようになります。
画面の左上に画像とテキストが重なって表示されていることがわかります。
デフォルトではこのような表示になります。

contentAlignment
次に contentAlignment を変更してみます。
contentAlignment では Boxの子要素の配置 を変更できます。
Flutter の場合は Stack の alignment で定義することが多いかと思います。
![]() |
見た目 | ||
|---|---|---|---|
| TopStart | Alignment.TopStart | Alignment.topLeft | ![]() |
| TopCenter | Alignment.TopCenter | Alignment.topCenter | ![]() |
| TopEnd | Alignment.TopEnd | Alignment.topRight | ![]() |
| CenterStart | Alignment.CenterStart | Alignment.centerLeft | ![]() |
| Center | Alignment.Center | Alignment.center | ![]() |
| CenterEnd | Alignment.CenterEnd | Alignment.centerRight | ![]() |
| BottomStart | Alignment.BottomStart | Alignment.bottomLeft | ![]() |
| BottomCenter | Alignment.BottomCenter | Alignment.bottomCenter | ![]() |
| BottomEnd | Alignment.BottomEnd | Alignment.bottomRight | ![]() |
Spacer
次に Spacer についてみていきます。
以下のような Spacer のサンプルを実行してみます。
@Composable
fun SpacerSample(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Item 1")
Spacer(modifier = Modifier.height(20.dp))
Text(text = "Item 2")
Spacer(modifier = Modifier.height(20.dp))
Text(text = "Item 3")
}
}
実行結果は以下のようになります。

modifier
Spacer では modifier プロパティのみを引数に指定することができ、かつ必須のプロパティになっています。Column の中では Spacer の modifier に Modifier.height(20.dp) のように指定することで、各要素間の高さを確保することができます。
Flutter の場合では SizedBox を用いた以下のような実装になるかと思います。
Column(
children: [
Text('Item 1'),
SizedBox(height: 20),
Text('Item 2'),
SizedBox(height: 20),
Text('Item 3'),
],
),
Layout
最後に Layout についてみていきます。
Layout では今まで見てきたものよりもさらに細かくレイアウトを決めることができます。
以下のようなコードを実行してみます。
@Composable
fun CustomLayoutModifierSample(modifier: Modifier = Modifier) {
Layout(
modifier = modifier
.fillMaxWidth()
.padding(16.dp),
content = {
Text(text = "Item 1")
Text(text = "Item 2")
}
) { measurable, constraints ->
layout(constraints.minWidth, constraints.maxHeight) {
val item1 = measurable[0].measure(Constraints())
val item1Position = Alignment.Center.align(
size = IntSize(item1.width, item1.height),
space = IntSize(constraints.maxWidth, constraints.maxHeight),
layoutDirection,
)
item1.place(item1Position)
val item2 = measurable[1].measure(Constraints())
val item2Position = item1Position.copy(x = item1Position.x + 100, y = item1Position.y + 100)
item2.place(item2Position)
}
}
}
実行結果は以下のようになります。

コードを詳しくみていきます。
以下では、 Layout の中で modifier と content を定義しています。
content では名前の通り、 Layout の中で表示させる Composable を定義できます。
以下ではテキストを二つ表示させています。
@Composable
fun CustomLayoutModifierSample(modifier: Modifier = Modifier) {
Layout(
modifier = modifier
.fillMaxWidth()
.padding(16.dp),
content = {
Text(text = "Item 1")
Text(text = "Item 2")
}
以下では、 layout メソッドを実装しています。
measurable には表示させる Composable のリストが格納されています。
item1 には measurable の一つ目の要素である Text(text = "Item 1") が格納されており、 measure メソッドで item1 のサイズを測っています。
item1Position では画面の中央の位置を保持しています。
item1.place(item1Position) では画面中央の位置に item1 を配置しています。
item2 には measurable の二つ目の要素である Text(text = "Item 2") が格納されており、その位置を item2Position として定義しています。
item2Position には、 item1Position の位置をもとにした位置を指定しています。具体的には、 item1Position の x軸、y軸の両方に 100 を足した値を指定しています。
item2 も同様に place メソッドで item2 を配置しています。
) { measurable, constraints ->
layout(constraints.minWidth, constraints.maxHeight) {
val item1 = measurable[0].measure(Constraints())
val item1Position = Alignment.Center.align(
size = IntSize(item1.width, item1.height),
space = IntSize(constraints.maxWidth, constraints.maxHeight),
layoutDirection,
)
item1.place(item1Position)
val item2 = measurable[1].measure(Constraints())
val item2Position = item1Position.copy(x = item1Position.x + 100, y = item1Position.y + 100)
item2.place(item2Position)
}
}
このように、 Layout ではより細かいレイアウトの指定をすることができ、上記のように特定の要素の相対的な位置も指定することができます。
相対的な位置を指定することで以下のようなUIも実装することができます。

上記UIのコード
@Composable
fun CustomLayoutModifierSample(modifier: Modifier = Modifier) {
Layout(
modifier = modifier
.fillMaxSize()
.background(Color.Black)
.padding(16.dp),
content = {
Image(
painter = painterResource(id = R.drawable.sun),
contentDescription = "Sun Image",
modifier = Modifier
.width(80.dp)
.height(80.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.mercury),
contentDescription = "Mercury Image",
modifier = Modifier
.width(20.dp)
.height(20.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.venus),
contentDescription = "Venus Image",
modifier = Modifier
.width(20.dp)
.height(20.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.earth),
contentDescription = "Earth Image",
modifier = Modifier
.width(30.dp)
.height(30.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.mars),
contentDescription = "Mars Image",
modifier = Modifier
.width(30.dp)
.height(30.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.jupiter),
contentDescription = "Jupiter Image",
modifier = Modifier
.width(50.dp)
.height(50.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.saturn),
contentDescription = "Saturn Image",
modifier = Modifier
.width(50.dp)
.height(50.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.uranus),
contentDescription = "Uranus Image",
modifier = Modifier
.width(50.dp)
.height(50.dp),
contentScale = ContentScale.Crop
)
Image(
painter = painterResource(id = R.drawable.neptune),
contentDescription = "Neptune Image",
modifier = Modifier
.width(50.dp)
.height(50.dp),
contentScale = ContentScale.Crop
)
}
) { measurable, constraints ->
layout(constraints.minWidth, constraints.maxHeight) {
val sun = measurable[0].measure(Constraints())
val sunPosition = Alignment.TopStart.align(
size = IntSize(sun.width, sun.height),
space = IntSize(constraints.maxWidth, constraints.maxHeight),
layoutDirection,
)
sun.place(sunPosition)
val mercury = measurable[1].measure(Constraints())
val mercuryPosition = sunPosition.copy(x = sunPosition.x + 100, y = sunPosition.y + 250)
mercury.place(mercuryPosition)
val venus = measurable[2].measure(Constraints())
val venusPosition = mercuryPosition.copy(x = mercuryPosition.x + 100, y = mercuryPosition.y + 250)
venus.place(venusPosition)
val earth = measurable[3].measure(Constraints())
val earthPosition = venusPosition.copy(x = venusPosition.x + 100, y = venusPosition.y + 250)
earth.place(earthPosition)
val mars = measurable[4].measure(Constraints())
val marsPosition = earthPosition.copy(x = earthPosition.x + 100, y = earthPosition.y + 250)
mars.place(marsPosition)
val jupiter = measurable[5].measure(Constraints())
val jupiterPosition = marsPosition.copy(x = marsPosition.x + 100, y = marsPosition.y + 250)
jupiter.place(jupiterPosition)
val saturn = measurable[6].measure(Constraints())
val saturnPosition = jupiterPosition.copy(x = jupiterPosition.x + 100, y = jupiterPosition.y + 250)
saturn.place(saturnPosition)
val uranus = measurable[7].measure(Constraints())
val uranusPosition = saturnPosition.copy(x = saturnPosition.x + 100, y = saturnPosition.y + 250)
uranus.place(uranusPosition)
val neptune = measurable[8].measure(Constraints())
val neptunePosition = uranusPosition.copy(x = uranusPosition.x + 100, y = uranusPosition.y + 250)
neptune.place(neptunePosition)
}
}
}
以上です。
まとめ
最後まで読んでいただいてありがとうございました。
調べていく中で、Flutter と似ている部分も多くあることがわかりましたが、細かいプロパティの指定方法等に差異があることもわかりました。
どちらかの書き方に慣れていると、わからなくなることもあると思うので、備忘録として活用いただければ幸いです。
誤っている点やもっと良い書き方があればご指摘いただければ幸いです。
参考





























Discussion