【Jetpack Compose】ColumnとRowで知っていると便利なこと3選

## はじめに

本記事は [with Advent Calendar 2021](https://qiita.com/advent-calendar/2021/with) 1日目の記事です。

こんにちは [with](https://with.is/welcome) でAndroid エンジニアをしている石田です。withでは Jetpack Compose を段階的にプロダクトに導入しています。

本記事では、Compose の基本レイアウトである ColumnとRow で個人的によく使う **便利だなと感じたユースケースを3つ** 紹介します。


## Weightを設定しつつ可能ならば詰めてレイアウトする


![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/71f764c2-8ffb-76d7-a805-4cf9714798d3.png)

Column/Row のスコープでは `Modifier.weight` で Weight を指定することができ、指定した値の比率で子の高さもしくは幅が決定されます。通常は内容に関係なくその比率の分だけ領域が確保されるますが、可能ならば詰めてレイアウトしたい場合はどうすればいいでしょうか。

実は `Modifier.weight()` は **第2引数に `fill: Boolean` 取ることができて** 、 `fill=false` を指定することで必要なだけ高さもしくは幅を取るという挙動にすることができます。これは ConstraintLayout で言うところの `app:layout_constraintHeight_default="wrap"` に相当します。


### サンプルコード

以下の要件を満たすサンプルを示します。

1. 名前の幅は伸縮し、年齢は名前の横に詰めてレイアウトする
1. 名前が長い場合は末尾を省略する
1. 年齢は常に省略されない

```kotlin
@Composable
fun WeightFillFalseExample() {
    Column {
        UserItem("なおと", 26)
        UserItem("なおと@Qiita Advent Calendar 2021参加中", 26)
    }
}

@Composable
private fun UserItem(
    name: String,
    age: Int,
) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.padding(16.dp)
    ) {
        Text(
            text = name,
            modifier = Modifier.weight(weight = 1f, fill = false), // < -- ココ!
            style = MaterialTheme.typography.bodyMedium,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
        )
        Spacer(modifier = Modifier.width(16.dp))
        Text(
            text = "${age}歳",
            style = MaterialTheme.typography.bodySmall,
        )
    }
}
```


## 子の間に均等にスペースを空けたい


![fig2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/86339195-896f-d07a-efad-2c2a414f4857.png)

ColumnやRowで子をスペースを空けながら等間隔に並べたい場合は  `Arrangement.spacedBy` を使うと便利です。これは ConstraintLayout の Flow で言うところの`app:flow_horizontalGap` / `app:flow_verticalGap` に相当します。 `Modifier.padding` や `Spacer` を使う方法に比べて コンポーザブルのツリー構造をシンプルに保つことができ、コードの見通しも良くなると思います。


### サンプルコード

サンプルを示します。 `LazyRow`に対して `Arrangement.spacedBy` を使ってますが、 `Row` でも同様です。Columnの場合は `horizontalArrangement` の部分が `verticalArrangement` になります。

```kotlin
@Composable
fun ArrangementSpacedbyExample() {
    LazyRow(
        contentPadding = PaddingValues(all = 16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp),     // < -- ココ!
    ) {
        item { Tag(text = "焼き肉🍖") }
        item { Tag(text = "お寿司🍣") }
        item { Tag(text = "ピザ🍕") }
        item { Tag(text = "お弁当🍱") }
    }
}

@Composable
private fun Tag(text: String) {
    Box(
        modifier = Modifier
            .clip(RoundedCornerShape(100))
            .background(MaterialTheme.colorScheme.secondaryContainer)
            .padding(horizontal = 12.dp, vertical = 4.dp),
    ) {
        Text(
            text = text,
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSecondaryContainer,
        )
    }
}
```

## 子を右から左に配置したい

![fig3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/65a1fb28-fe0f-8d4c-0a94-146957aa43e8.png)

Rowは通常 子を左から右に配置していきますが、右から左に配置することも可能です。配置の方向を変えるには `LocalLayoutDirection` の値を設定します。


### サンプルコード

サンプルを示します。 `LocalLayoutDirection` の値は `CompositionLocalProvider` を使って指定します。

```kotlin
@Composable
fun LayoutDirectionExample() {
    Column {
        ProvideLayoutDirection(layoutDirection = LayoutDirection.Ltr) {
            Seasons()
        }

        ProvideLayoutDirection(layoutDirection = LayoutDirection.Rtl) {
            Seasons()
        }
    }
}

@Composable
private fun ProvideLayoutDirection(
    layoutDirection: LayoutDirection,
    content: @Composable () -> Unit,
) {
    CompositionLocalProvider(
        LocalLayoutDirection provides layoutDirection, // < -- ココ!
        content = content,
    )
}

@Composable
private fun Seasons() {
    Row(
        modifier = Modifier.padding(16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp),
    ) {
        Text(text = "春")
        Text(text = "夏")
        Text(text = "秋")
        Text(text = "冬")
    }
}
```

## まとめ

ColumnとRow で個人的によく使う便利だなと感じたユースケースを3つ紹介しました。

[動作可能なサンプルコードをGitHubで公開している](https://github.com/maxfie1d/column-row-example) のでこちらも併せてご参照ください。