ViewGroup全体をお手軽に角丸にする

Viewに角丸をつける方法は様々あります。
以下のように、複雑なレイアウトを角丸に切り取って表示する方法を紹介します。

|Layout|角丸化|
|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/c9b4d224-94a4-36b6-7db1-0b21133d331d.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/b6b2235d-115e-a37a-5adf-46ab4c469b48.png)|

ViewGroupを継承してdraw処理をoverrideしてしまえば何でもありになります。
ただ、手間も大きいため、これ以外の方法を考えてみます。

# CardViewで囲む

お手軽な方法としてはこれですね。CardViewはこのような角丸なViewを作るためのViewなので、以下のように指定して、角丸にしたいレイアウト全体を囲めば最初の例にあるような角丸をつけることができます。

```xml
<androidx.cardview.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:cardCornerRadius="200dp"
    app:cardElevation="0dp"
    >
    ...
</androidx.cardview.widget.CardView>
```

デメリットとしてはViewの階層が余分に一つ必要になるところでしょうか？

# background + clipToOutline を使う

xmlでshapeを定義するGradientGrawableを設定し、clipToOutlineを設定することでも角丸にできます。
例えば、以下のように角丸を設定したGradientDrawableを定義し、

```round.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <corners android:radius="200dp" />
    <solid android:color="#00FF00" />
</shape>
```

以下のようにbackgroundとしてこのdrawableを指定、`android:clipToOutline="true"`を設定します。
xmlでclipToOutlineを設定できるのはAPI 31以上です。
かなり新しい環境でしか使えませんが、コード上から指定する方法であれば、API 21から使えますので、`clipToOutline = true`を設定することでほとんどの環境で使える方法です。

```xml
<LinearLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/round"
    android:clipToOutline="true"
    android:orientation="vertical"
    />
```

これで背景に設定したDrawableのshapeに併せて全体がclipされ、角丸表示となります。
clip処理に必要なのはshapeの情報だけですので、背景の指定が必要ない場合は、`solid`タグのない、GradientDrawableを指定することで、その形状に切り取って表示できます。

```round.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <corners android:radius="200dp" />
</shape>
```

デメリットとしてはbackgroundにshapeを指定する必要があるため、別のbackgroundを指定したい場合は別のViewで囲むなどが必要になるところでしょうか？

# ViewOutlineProvider + clipToOutline を使う

前項の方法は内部実装としてBackgroundDrawableをベースとしたViewOutlineProviderが使われています。

```ViewOutlineProvider.java
    public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            Drawable background = view.getBackground();
            if (background != null) {
                background.getOutline(outline);
            } else {
                outline.setRect(0, 0, view.getWidth(), view.getHeight());
                outline.setAlpha(0.0f);
            }
        }
    };
```

Backgroundとは別に独立したViewOutlineProviderを指定することで自由に形状を指定することができます。

インラインクラスを使って以下のような実装をしても良いでしょうし、角丸の半径を指定できる汎用クラスを作るのも良いですね。

```kotlin
binding.container.clipToOutline = true
binding.container.outlineProvider = object : ViewOutlineProvider() {
    override fun getOutline(view: View, outline: Outline) {
        outline.setRoundRect(0, 0, view.width, view.height, 200 * density)
    }
}
```

また、Drawableを指定して、そのDrawableの形状を反映させるViewOutlineProviderを作るという方法もあります。

```kotlin
class DrawableOutlineProvider(
    private val drawable: Drawable?,
) : ViewOutlineProvider() {
    private val rect: Rect = Rect()

    override fun getOutline(view: View, outline: Outline) {
        rect.set(0, 0, view.width, view.height)
        drawable?.let {
            it.bounds = rect
            it.getOutline(outline)
        }
    }
}
```

この場合、drawableはViewと独立しているため、Viewのサイズを反映させてからoutlineを書き出す必要がある点に注意です。

デメリットは、他の方法に比べて実装量が多くなるところでしょうか？

その代わり自由度が高い指定をすることができます。

---

以上です