【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