［Android］カスタムビューの作り方 (複合コンポーネント)

# はじめに

Android では[公式](https://developer.android.com/guide/topics/ui/custom-components)にあるとおりカスタムビューを作成する方法が何パターンかあります。
今回は複合コンポーネントを作成してみたので作成方法をまとめたいと思います。

| 名称 | 説明 |
| --- | --- |
| フルカスタムコンポーネント | `View`を継承していちから新たなコンポーネントを作成するパターン |
| 複合コンポーネント | 既存のコンポーネントを複数組み合わせて新たなコンポーネントを作成するパターン |

![mermaid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/ad67be1d-78a4-4abf-75ec-a915df29d736.png)

今回は次のような3つのボタンをまとめ、選択したボタンのテキストが
赤く表示される複合コンポーネントを作成したいと思います。

![Dec-30-2019 20-05-56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/23bf0cba-1a27-58f4-c78d-0f9d2a0dc60f.gif)


# 1. `Container`クラスを継承する
複合コンポーネントを作成するためには、まずどの`Container`クラスを継承するか決めます。
今回は`LinearLayout`を利用するので次のように実装します。

```SelectorView.kt
class SelectorView(context : Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) 
```

このように`LinearLayout`を継承したクラスを定義するとレイアウトで`SelectorView`を記述できるようになります。

```main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
  
    <kaleidot725.customviewsample.SelectorView
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>
```

# 2. 複数のコンポーネントを組み合わせたレイアウトを作成して読み込む

次に複合コンポーネントで読み込むレイアウトを作成します。
3つのボタンを並べた複合コンポーネントを作成したいので、
次のように`merger`の中に3つ`Button`を入れたレイアウトを作成します。

```selector_view.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <Button
        android:id="@+id/one"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="1"/>

    <Button
        android:id="@+id/two"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="2"/>

    <Button
        android:id="@+id/three"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="3"/>
</merge>
```

そして複合コンポーネントで`View.inflate`を利用して読み込めば
複数のコンポーネントを組み合わせたカスタムビューを実装できます。

```SelectorView.kt
class SelectorView(context : Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {
    init {
        View.inflate(context, R.layout.selector_view, this)
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/72e9c875-3189-4b36-5da7-d075fa80528a.png" height=300 />

# 3. 複数のコンポーネントを組み合わせて処理を実装する。

レイアウトで定義した複数コンポーネントを制御する処理を実装し、
独自の複合コンポーネントならではの制御ができるようにします。
次のように選択したボタンのテキスト色を変え、他のボタンのテキスト色を戻す処理を実装してみます。

```kotlin
class SelectorView(context : Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {

    val oneButton : Button
    val twoButton : Button
    val threeButton : Button

    init {
        View.inflate(context, R.layout.selector_view, this)

        oneButton = findViewById(R.id.one)
        twoButton = findViewById(R.id.two)
        threeButton = findViewById(R.id.three)

        oneButton.setOnClickListener {
            oneButton.setTextColor(Color.RED)
            twoButton.setTextColor(Color.BLACK)
            threeButton.setTextColor(Color.BLACK)
        }

        twoButton.setOnClickListener {
            oneButton.setTextColor(Color.BLACK)
            twoButton.setTextColor(Color.RED)
            threeButton.setTextColor(Color.BLACK)
        }

        threeButton.setOnClickListener {
            oneButton.setTextColor(Color.BLACK)
            twoButton.setTextColor(Color.BLACK)
            threeButton.setTextColor(Color.RED)
        }
    }
}
```

![Dec-30-2019 20-05-56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/23bf0cba-1a27-58f4-c78d-0f9d2a0dc60f.gif)


# ＊. なぜ`merger`を使うのか？

`merger`ですが無駄な`View`を生成しないために利用しています。
例えば`merger`を`LinearLayout`に置き換えたレイアウトを
複合コンポーネントで読み込むと次の構造で`View`が生成されてしまいます。

![mermaid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/9e6da031-e02e-a40f-93d4-2fee21cb17e4.png)

ですが`merger`を利用すると次の構造で`View`が生成されるので`無駄に`View`を生成を避けられます。
無駄に`View`を生成するとUIパフォーマンスの低下に繋がるので`merger`を利用したほうがよいです。

![mermaid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/491fc7b1-1ed3-8799-881d-e643b50fe509.png)

# おわりに

今回作成したサンプルは次にまとめています。
なので必要に応じて閲覧していただければと思います。

<a href="https://github.com/kaleidot725/android-mylab/tree/master/CustomViewSample"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/180757ac-284d-78e8-4a7e-2eff07903da1.png" height=125/></a>

# 参考文献

- [Custom View Component | Android Developers](https://developer.android.com/guide/topics/ui/custom-components)
- [Canvas | Android Developers](https://developer.android.com/reference/android/graphics/Canvas)
- [Reusing Layout | Android Developers](https://developer.android.com/training/improving-layouts/reusing-layouts)