📻

Android View でラジオボタンを実装する際の選択肢と落とし穴

2022/12/03に公開

概要

この記事は bitFlyer Advent Calendar 2022 の3日目です。

用意された選択肢の一つを必ず選択する UI コンポーネントとして、ラジオボタンがあります。Android View の中でも API level 1 から RadioGroup / RadioButton という実装が存在しますが、RadioGroup / RadioButton 以外を使ってラジオボタンを実現する選択肢も存在します。この記事では、RadioGroup / RadioButton 以外の選択肢の紹介と、それぞれの選択肢のメリット・デメリットを解説します。

この記事で用いる Material Design のライブラリは以下の通りです。

com.google.android.material:material:1.7.0

また確認した際の MaterialDesignComponent for Android の commit hash は以下です。
https://github.com/material-components/material-components-android/commit/355c69d2c958d6eba37284182e608f09c49db558

注意点

この記事中では、具体的な実装については触れず、実装の選択肢と落とし穴についてのみ解説します。

選択肢とメリット・デメリットの概要

RadioGroup / RadioBUtton で実装する選択肢を含め、ラジオボタンを実現するには以下のような方法があります。メリット・デメリットに応じて使い分けましょう。

  • RadioGroup / RadioButton を使う
  • MaterialButtonToggleGroup / MaterialButton を使う
  • ChipGroup / Chip を使う
メリット デメリット
RadioGroup / RadioButton 他の選択肢に比べ制約が少なく自由度が高く実装できる 自由度が高い反面、目的の表示やタッチした際のエフェクトを意図通りに実装したい場合、実装コストがやや高くなる
MaterialButtonToggleGroup / MaterialButton MaterialButton をベースに実装できるため MaterialDesign に準拠したデザイン実装が容易 各要素間のマージンが反映されない。また各要素の Shape は、要素の隣接する箇所では反映されない
ChipGroup / Chip MaterialDesign に準拠したデザイン実装が容易 ChipGroup が特殊なレイアウトであるため、「横幅いっぱいに広げた上で各要素で幅を等分する」レイアウトは実現できない

以下で各選択肢を見ていきましょう。

選択肢の詳細

RadioGroup / RadioButton の場合

RadioGroup / RadioButton は Android API level 1 から存在する ViewGroup / View です。ラジオボタンを実装する際にはこの UI コンポーネントが真っ先に思い浮かぶことでしょう。

しかしながら Material Design Component の UI コンポーネントではないため、Material Design に準拠して色やリップルエフェクトを適用しようとした場合、自身で color selector や ripple drawable を実装する必要が出てきます(下図)

MaterialButtonToggleGroup / MaterialButton の場合

MaterialButtonToggleGroup は Material Design Component の中の UI コンポーネントです。MaterialButton を子要素として持つことができ、複数要素の選択機能などを実装することができます。この中で必ず一つの要素を選択させるようにすることで、ラジオボタンとして実装することが可能です。

<com.google.android.material.button.MaterialButtonToggleGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:selectionRequired="true"
    app:singleSelection="true">
		
    <com.google.android.material.button.MaterialButton
        style="@Widget.App.ToggleButton"
	android:layout_width="0dp"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:text="@string/choice_1" />
				       
</com.google.android.material.button.MaterialButtonToggleGroup>

Material 2 の中で登場した UI コンポーネントですが、Material 3 においても Segmented Buttons として定義されています。 Jetpack Compose での実装はまだ存在しませんが、Status が "Planned" となっているため今後実装されることでしょう。

MaterialButtonToggleGroup / MaterialButton の落とし穴

MaterialButtonToggleGroup / MaterialButton の組み合わせを使った場合、デザインによってはそのデザインを実現することができません。というのも MaterialButtonToggleGroup の子要素となった MaterialButton には、以下の変更が MaterialButtonToggleGroup から適用されるためです。

  • MaterialButton 間のマージンを削除
  • MaterialButton が隣接する箇所から、適用されている Shape を削除

このため例えばデザインが「ラジオボタン間にマージンが存在する」であったり「各ラジオボタンには角丸を設定する」などのデザインであった場合、MaterialButtonToggleGroup ではその要件を満たすことができません(下図)。

ChipGroup / Chip の場合

ChipGroup / Chip も MaterialButtonToggleGroup と同様に Material Design Component の中の UI コンポーネントです。Chip はユーザーの入力、選択、フィルタリング、アクションのトリガーとして用いることが想定されています[1] この中で Choice chip という種類の Chip を用い、ChipGroup の attribute を変更することでラジオボタンの代替として実装することができます。Material Design Component の UI コンポーネントであるため、 Material Design 準拠のデザインとすることも容易です。

<com.google.android.material.chip.ChipGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:selectionRequired="true"
    app:singleSelection="true">

    <com.google.android.material.chip.Chip
        style="@style/Widget.MaterialComponents.Chip.ChoiceChip"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
        android:text="@string/choice_1" />

</com.google.android.material.chip.ChipGroup>

Material 3 でも Material 2 と同様に Chips として定義がされています。 Material 2 と Material 3 との間で定義に変更が入りましたが、ラジオボタンとして用いる際の Choice Chips は Material 3 でも利用可能です。また Jetpack Compose の Material 3 実装にもすでに用意されているため、 Jetpack Compose から用いることもできます。

ChipGroup / Chip の落とし穴

ChipGroup / Chip の組み合わせを使った場合、特定のケースで意図通りのレイアウトを組むことができません。この特定のケースとは「ChipGroup を画面幅一杯に広げた上で Chip の各要素の幅を等分する」ようなレイアウトを組みたいケースです(下図)

このような制約が出る原因は、ChipGroup が FlowLayout という特殊なレイアウトを用いて実現されているためです。

FlowLayout

FlowLayout は MaterialDesignComponent の内部で実装された ViewGroup であり、LinearLayout と同じように子要素を水平方向に並べることができます。しかし LiearLayout と違う点は、子要素が端まで並べられた際に折り返して子要素を並べ続けることができる点です。

FlowLayout の Javadoc
https://github.com/material-components/material-components-android/blob/355c69d2c958d6eba37284182e608f09c49db558/lib/java/com/google/android/material/internal/FlowLayout.java#L36-L44

逆にこの特性があるために、この FlowLayout では「画面幅一杯に FlowLayout を広げた上で layout_weight などを用いて各要素で幅を当分する」といった実装ができません。

ChipGroup / Chip でラジオボタンを実現したい場合、少ない選択肢を画面の端に並べて置くデザインに留めるほうがよいでしょう。

まとめ

ラジオボタンを実現する際に RadioGroup / RadioButton 以外の選択肢が存在します。Material Design 準拠の UI コンポーネントである MaterialButtonToggleGroup や ChipGroup がその選択肢ではありますが、これらのコンポーネントには制約が存在します。これらの制約を把握した上で適切にコンポーネントを選択していきましょう。

終わりに

弊社 bitFlyer では Android エンジニアを募集しています。ご興味のある方はぜひご応募ください。カジュアル面談も大歓迎いたします!

脚注
  1. https://m2.material.io/components/chips#usage ↩︎

Discussion