ComposeのCornerSizeの指定

# 初めに

[ポート株式会社 サービス開発部 Advent Calendar 2023](https://adventar.org/calendars/9156) 22日目の記事です。

![スクリーンショット 2023-12-22 10.34.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/535aefcf-0008-7ed2-d946-16c92c5e4cf0.png)

今回は、細かいところではありますが、[`CornerSize`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/shape/CornerSize)の指定方法の違いと注意することについて、解説していきます。

公式ドキュメントの`androidx.compose.foundation.shape`に書かれている内容がほとんどなので、こちらを参考にしてください。

https://developer.android.com/reference/kotlin/androidx/compose/foundation/shape/package-summary

## 結論

最初に、僕が伝えたいことを述べたいと思います。

これから解説する`CornerSize`には、指定方法がいくつもあるので、**どんな端末でもデザインを崩さない**ように、適切な指定方法を選んでいきましょう。

## きっかけ

自分が携わっているプロジェクトでは、`Button`や`Chip`、`Card`などの多くのコンポーネントを角丸にしています。

アプリの動作確認を、複数端末で見比べながら行っていた時、ある端末ではデザイン通りのUIになっていましたが、別の端末ではデザイン崩れを起こしているのを見つけました。

この修正作業を行った際に、`CornerSize`の仕様について調べたというのがきっかけです。

## Shape

まず、Androidには`Shape`があり、このクラスを指定するとコンポーネントの形が変化します。

### AbsoluteCutCornerShape, CutCornerShape

角が削られた形を生成するクラスです。

![スクリーンショット 2023-12-22 12.59.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/ad38aaea-e379-c97f-0cc1-7d5a3e03e47b.png)

`AbsoluteCutCornerShape`と`CutCornerShape`の違いは、右から左へのレイアウト方向（[`LayoutDirection.Rtl`](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/LayoutDirection#Rtl)）の`CornerSize`が考慮されているかどうかです。

4方向の角を指定するHelperメソッドを見てみるとわかりやすいです。

- https://developer.android.com/reference/kotlin/androidx/compose/foundation/shape/package-summary#AbsoluteCutCornerShape(androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp)
- https://developer.android.com/reference/kotlin/androidx/compose/foundation/shape/package-summary#CutCornerShape(androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp)


```kotlin
// left・rightの関係
fun AbsoluteCutCornerShape(
    topLeft: Dp = 0.dp,
    topRight: Dp = 0.dp,
    bottomRight: Dp = 0.dp,
    bottomLeft: Dp = 0.dp
): AbsoluteCutCornerShape

// start・endの関係
fun CutCornerShape(
    topStart: Dp = 0.dp,
    topEnd: Dp = 0.dp,
    bottomEnd: Dp = 0.dp,
    bottomStart: Dp = 0.dp
): CutCornerShape
```

### AbsoluteRoundedCornerShape, RoundedCornerShape

角が丸くした形を生成するクラスです。

![スクリーンショット 2023-12-22 13.00.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/73f23460-8c5e-18aa-62e4-f90a82a2fb81.png)

`CutCornerShape`と同様、`AbsoluteRoundedCornerShape`と`RoundedCornerShape`の違いは、右から左へのレイアウト方向の`CornerSize`が考慮されているかどうかです。


#### CircleShape

`RoundedCornerShape`の一種で、円形に生成するクラスです。

![スクリーンショット 2023-12-22 13.09.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/e0240812-73d9-e1c4-229e-cdbfa062fbdc.png)

`CircleShape`は、`RoundedCornerShape(50)`と同義になります。

```kotlin
CircleShape == RoundedCornerShape(50)
```

### GenericShape

[`Path`](https://developer.android.com/reference/android/graphics/Path)を使用して、独自に形を生成することもできます。

例：台形

```kotlin
GenericShape { size, _ ->
    // 始点
    moveTo(size.width * 1 / 3, 0f)

    // 台形になるように線を引く
    lineTo(size.width * 2 / 3, 0f)
    lineTo(size.width, size.height)
    lineTo(0f, size.height)
    lineTo(size.width * 1 / 3, 0f)
}
```

![スクリーンショット 2023-12-22 13.18.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/5cb84c84-1311-9f1d-0951-9b4357fde605.png)

## CornerSize指定

（ここから本題です。）

Composeであらかじめ用意されている`Shape`（`CutCornerShape`、`RoundedCornerShape`）には、上述のようなHelperメソッドが用意されていて、そのメソッドを使用することで`Shape`を生成します。

今回は、`RoundedCornerShape`を見ていきます。

```kotlin
fun RoundedCornerShape(corner: CornerSize): RoundedCornerShape

fun RoundedCornerShape(percent: Int): RoundedCornerShape

fun RoundedCornerShape(size: Dp): RoundedCornerShape

fun RoundedCornerShape(size: Float): RoundedCornerShape

fun RoundedCornerShape(
    topStart: Dp = 0.dp,
    topEnd: Dp = 0.dp,
    bottomEnd: Dp = 0.dp,
    bottomStart: Dp = 0.dp
): RoundedCornerShape

fun RoundedCornerShape(
    topStart: Float = 0.0f,
    topEnd: Float = 0.0f,
    bottomEnd: Float = 0.0f,
    bottomStart: Float = 0.0f
): RoundedCornerShape

fun RoundedCornerShape(
    topStartPercent: @IntRange(from = 0, to = 100) Int = 0,
    topEndPercent: @IntRange(from = 0, to = 100) Int = 0,
    bottomEndPercent: @IntRange(from = 0, to = 100) Int = 0,
    bottomStartPercent: @IntRange(from = 0, to = 100) Int = 0
): RoundedCornerShape
```

上記で挙げたメソッドは全て、引数の値を`CornerSize`に変換し、`RoundedCornerShape`に渡しています。

### `CornerSize`の指定方法

`CornerSize`に変換する場合は、以下のメソッドを使用しています。

https://androidx.tech/artifacts/compose.foundation/foundation/1.0.0-alpha04-source/androidx/compose/foundation/shape/CornerSize.kt.html

```kotlin
fun CornerSize(percent: @IntRange(from = 0, to = 100) Int): CornerSize

fun CornerSize(size: Dp): CornerSize

fun CornerSize(size: Float): CornerSize
```

3つの指定方法があり、おおまかに**割合** or **サイズ（絶対値）** で指定します。

#### 割合

0 ~ 100の**Int**を指定することで、コンポーネントサイズに対しての指定した割合で角サイズを決定します。

0よりも小さい場合または100よりも大きい場合は、エラーになります。

#### サイズ（絶対値）

サイズは、`dp`と`px`の2つの指定方法があります。

`androidx.compose.ui.unit.Dp`を渡すと`dp`指定、`Float`を渡すと`px`指定になり、固定値で角サイズを決定します。

## 注意すること

「`CornerSize`の指定方法」でお気づきになるかもしれないですが、`CornerSize`を指定する時、**`Int`と`Float`で意味合いが違います。**（当然ですね。。。）

多くの場合は、割合 == `Float`のイメージだと思っているので、割合で指定したい時に`Float`で渡しがちになります。（自分はそう思ってました。）

しかし、`Float`で渡してしまうと、`px`指定になってしまうため、デバイス密度に依存してしまいます。
そのため、特定のデバイスではデザイン通りになっていても、他のデバイスでは角サイズが足りていないという現象が起きてしまいます。

例： `CornerSize`を`50f`で指定する。

|　Pixel 7 Pro | Pixle 6a |
| --- | --- |
| <img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/4bc1ec40-9ca3-c6e2-6f28-d7077598533a.png width=300 /> | <img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179470/279d2248-bb26-f4e8-36cc-1393d8d3fef7.png width=300 /> |


## 終わりに

このように、端末によってデザインが変わってしまうのは、デザインを作成したデザイナーさんも意図していないことが多いので、どんな端末でもデザインを崩さないように、適切な指定方法を行うことを意識していきましょう！

今回の僕の場合では、UIテストを端末ごと（特にDpiが違う端末）で行い、デザインが正しいかをチェックすることを担保することでも回避できることなので、ComposeでのUIテストもちゃんと学んできたいなとおもえる出来事だったと思います。


## 参考

今回、参考にしていただいた記事です。

- https://mahendranv.github.io/posts/compose-shapes/
- https://star-zero.medium.com/jetpack-compose%E3%81%A7%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0shape%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B-d907712199c1