Modifierの修飾子は書く順番がめっちゃ大事という話【Jetpack Compose】

# はじめに

Jetpack Composeで思い通りのUIを実装するには、Modifierの適用順序がとても大切です。本記事では、Modifierの修飾子の順番が見た目にどのような影響を与えるのかを、簡単にまとめます。


# 今回つくりたいもの
- 黒い円（200.dp）の中央に、白いチェックマークアイコン（150.dp）
- 円の上と左に50.dpの余白
<img width=50%, src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798901/6719f503-2527-4150-a8de-fbdf52815342.png">


# ✅ 正しい書き方

```kotlin
Box(
    modifier =
        Modifier
            .padding(start = 50.dp, top = 50.dp)
            .size(200.dp)
            .clip(shape = CircleShape)
            .background(color = Color.Black),
    contentAlignment = Alignment.Center,
) {
    Icon(
        imageVector = checkIconImageVector(),
        contentDescription = "check",
        tint = Color.White,
        modifier = Modifier.size(150.dp),
    )
}
```

この書き方では以下の順序で適用され、意図通りの見た目になります。

1.  `padding(start = 50.dp, top = 50.dp)`：親要素との間に余白を追加
2.  `size(200.dp)`：Boxサイズを指定
3.  `clip(shape = CircleShape)`：サイズ確定後のBoxを円形に切り抜き
4.  `background(color = Color.Black)`：クリップされた範囲内に背景色を描画

# 🈲 NG例とその理由


### 1. `clip`が効かないパターン
<img width=50%, src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798901/67ec3b99-458c-4cc6-ade4-bc147c4f66c3.png">

```kotlin
Box(
    modifier =
        Modifier
            .padding(start = 50.dp, top = 50.dp)
            .background(color = Color.Black) // ここで先に背景色が塗られてしまう
            .size(200.dp)
            .clip(shape = CircleShape),
    contentAlignment = Alignment.Center,
) {
    Icon(
    // 省略
    )
}
```
または
```kotlin
Box(
    modifier =
        Modifier
            .padding(start = 50.dp, top = 50.dp)
            .size(200.dp)
            .background(color = Color.Black) // ここで先に背景色が塗られてしまう
            .clip(shape = CircleShape),
    contentAlignment = Alignment.Center,
) {
    Icon(
    // 省略
    )
}
```
これらは円を切り取る前に背景色を描いてしまっており、後から`clip`をつなげても見た目が変わらない状態になってしまっています。


### 2. `clip`が不完全に適用されてしまうパターン
<img width=50%, src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798901/f8780ddc-eb00-4872-a5d1-e2bdaf7f1ae5.png">

```kotlin
Box(
    modifier =
        Modifier
            .clip(shape = CircleShape) // サイズが未定なのでうまくクリップできない
            .padding(start = 50.dp, top = 50.dp)
            .size(200.dp)
            .background(color = Color.Black),
    contentAlignment = Alignment.Center,
) {
    Icon(
    // 省略
    )
}
```
これだとサイズ未確定のまま`clip`を適用しているため、どこを切り抜けばいいのかを判断できず、円形にならない状態になっています。


### 3. 背景の円が小さくなるパターン
<img width=50%, src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798901/56669547-c410-4a7e-beb3-dffefa43c7bf.png">

```kotlin
Box(
    modifier =
        Modifier
            .size(200.dp) // ここでBoxのサイズが確定する
            .padding(start = 50.dp, top = 50.dp) // 作ったBoxの内部でPaddingを取る
            .clip(shape = CircleShape)
            .background(color = Color.Black),
    contentAlignment = Alignment.Center,
) {
    Icon(
    // 省略
    )
}
```
これだと、指定されたサイズの範囲内にpaddingが適用されてしまいます。つまり、200.dpの円を描くことはできず、200.dpのエリア内に余白を取ったうえでその残りのエリアに円を描く形になっているので、結果的に円が小さく押しつぶされてしまっています。


# 📗 まとめ： Modifierのcompose修飾子は、書いた順に描画される

ComposeのModifierは、関数型のデコレーターとして順に適用されるため、先に書かれた修飾子の影響を受けながら、後続の修飾子が処理されていきます。「必要な修飾子は書いているはずなのに、うまく効いてくれない(・Д・)」...そんな時は、つなぐ順番が適切かを見直してみるとうまくいくかも知れません。




### ⭐️ 参考資料
https://developer.android.com/jetpack/compose/modifiers