Compose UI:RadioButton

投稿日:  更新日:

Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。

そのライブラリ中のRadioButtonについて、構成や使用方法などをまとめます。

※環境:Android Studio Hedgehog | 2023.1.1
    Kotlin 1.8.10
    Compose Compiler 1.4.3

スポンサーリンク

UIの概要

RadioButtonはラジオボタンのボタン部分を表現するUI要素です。

ラジオボタンは2つ以上の項目から一つを選択するボタンです。常に一つだけが選択された状態になる特徴があります。

現在と別のボタンを選択すれば、旧ボタンは解除され、新ボタンが選択された状態になります。

RadioButtonの概要

ラジオボタンとしての動作を実現するために、RadioButtonは「2つ以上のボタン」と「ボタン間のセレクト処理」の実装が必要です。

また、RadioButtonはボタン部分のみなので、項目名(アイコン、画像なども可能)を表示するなど、ユーザに対する説明の配慮も必要です。

スポンサーリンク

関数の引数

RadioButtonの状態(コンテンツ、装飾、演出など)は、引数により指定できます。

@Composable
fun RadioButton(
    selected: Boolean,
    onClick: (() -> Unit)?,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    colors: RadioButtonColors = RadioButtonDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) { ... }
引数概要
selectedBooleanボタンの状態
true:選択/false:非選択
onClick( )->Unit?クリックで実行する関数オブジェクト(ラムダ式)
modifierModifierUI全般のCompose修飾子
enabledBooleanUIの有効:true/無効:false
※有効⇒クリック可能、無効⇒クリック不可
colorsRadioButtonColors選択あり/なし(selected)、有効/無効(enabled)に合わせた色の情報
interactionSourceMutableInteractionSourceユーザが行ったUI操作の観測と報告
スポンサーリンク

関数の構成

RadioButtonは下位階層にCanvas(Composable関数)を一つ持つ構成になっています。

このCanvasの引数onDrawに指定されるラムダ式で、ボタンの図形が描画(drawCircle)されています。

onDrawはViewシステム(前UIシステム)と同じ機能を有しますが、Canvasクラスのインスタンスが引数では無く、レシーバー(this)で与えられる点が異なります。

また、RadioButtonはボタンの状態(選択/非選択)を保持していません。入力された状態(引数selected)に合わせて、ボタンの形状と配色を変えているだけです。

選択されているボタンの管理はプログラマ側で実装します(後述、セレクト処理を参照)。

@Composable
fun RadioButton( ... ) {
    val dotRadius = animateDpAsState(
        targetValue = if (selected) RadioButtonDotSize / 2 else 0.dp,
        animationSpec = tween(durationMillis = RadioAnimationDuration)
    )
    val radioColor = colors.radioColor(enabled, selected)
    val selectableModifier =
        if (onClick != null) {
            Modifier.selectable(
                selected = selected,
                onClick = onClick,
                enabled = enabled,
                role = Role.RadioButton,
                interactionSource = interactionSource,
                indication = rememberRipple(
                    bounded = false,
                    radius = RadioButtonTokens.StateLayerSize / 2
                )
            )
        } else {
            Modifier
        }
    Canvas(
        modifier
            .then(
                if (onClick != null) {
                    Modifier.minimumInteractiveComponentSize()
                } else {
                    Modifier
                }
            )
            .then(selectableModifier)
            .wrapContentSize(Alignment.Center)
            .padding(RadioButtonPadding)
            .requiredSize(RadioButtonTokens.IconSize)
    ) {
        // Draw the radio button
        val strokeWidth = RadioStrokeWidth.toPx()
        drawCircle(
            radioColor.value,
            radius = (RadioButtonTokens.IconSize / 2).toPx() - strokeWidth / 2,
            style = Stroke(strokeWidth)
        )
        if (dotRadius.value > 0.dp) {
            drawCircle(radioColor.value, dotRadius.value.toPx() - strokeWidth / 2, style = Fill)
        }
    }
}
@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw))
スポンサーリンク

セレクト処理

セレクト処理を実装しないと、ラジオボタンとして動作しません。

@Composable
fun RadioButtonPanel(labels: Array<String>) {
    val _current = remember { mutableStateOf(labels.first()) }
    Column {
        labels.forEach {  // itはforEachの要素、項目名
            Row(verticalAlignment = Alignment.CenterVertically) {
                RadioButton(
                    selected = _current.value == it,   // 排他的な処理(2)
                    onClick = { _current.value = it }  // 排他的な処理(1)
                )
                Text(text = it, modifier = Modifier.fillMaxWidth())
            }
        }
    }
}

セレクト処理は2つのステップから成り立ちます。

 排他的な処理(1) 

ボタンのクリックで発生するイベントにより、onClick(ラムダ式)が実行されます。ここで、新しい状態を_currentへ保存します。これにり、関数RadioButtonPanelは再Composeの対象になります。

 排他的な処理(2) 

関数RadioButtonPanelが再Composeされます。実行されるRadioButtonにおいて、新しい状態と自分を比較し、選択⇔非選択(selectedのfalse/true)を切り替えます。

    private val labels = arrayOf("item0", "item1", "item2")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    RadioButtonPanel(labels = labels)
                }
            }
        }
    }
スポンサーリンク

サンプル:設定を行うActivity

RadioButtonはユーザがアプリの設定を行うような場面で多用されます。

サンプルはアプリの設定を行うActivityです。

データクラスRadioButtonDatasetを介して、ロジックからUIへ項目名を渡し、逆にUIからロジックへ選択の結果を返しています。

@Stable
data class RadioButtonDataset(val labels: Array<String>) {
    var selectedValue: String? = null
}

@Composable
fun RadioButtonPanel(dataset: RadioButtonDataset) {
    val _current = remember { mutableStateOf(dataset.selectedValue) }
    Column {
        dataset.labels.forEach {  // itはforEachの要素
            Row(verticalAlignment = Alignment.CenterVertically) {
                RadioButton(
                    selected = _current.value == it, // 排他的な処理
                    onClick = {
                        _current.value = it          // 排他的な処理
                        dataset.selectedValue = it   // ロジックへ返す
                    }
                )
                Text(text = it, modifier = Modifier.fillMaxWidth())
            }
        }
    }
}

ちなみに、UIツリーの描画処理(再Composeなど)は「onResume終了~onPause開始」の区間に行われます。

サンプルは、描画処理の前後のタイミングを利用して、onStartでボタンの初期状態を設定し、onStopで取得した選択値を処理しています。

ConfigurationActivity

※描画処理については「Jetpack Compose:Composeによるアプリ画面の描画」を参照

class ConfigurationActivity : ComponentActivity() {

    private val rbn012 = RadioButtonDataset(arrayOf("item0", "item1", "item2"))
    private val rbnABC = RadioButtonDataset(arrayOf("itemA", "itemB", "itemC"))

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // Configの画面を定義
                    Column {
                        RadioButtonPanel(dataset = rbn012)	// 動作設定1
                        Divider(color = Color.LightGray)
                        RadioButtonPanel(dataset = rbnABC)	// 動作設定2
                        Divider(color = Color.LightGray)
                    }
                }
            }
        }
    }

    override fun onStart() {
        super.onStart()
        // ラジオボタンの初期値を設定
        rbn012.selectedValue = rbn012.labels.last()
        // ※ABC側は初期値なし
    }

    /*
    ** onResumeの実行後、UIツリーの描画処理が開始される
    */

    override fun onStop() {
        super.onStop()
        // ラジオボタンの選択値を取得
        val _value012 = rbn012.selectedValue
        val _index012 = rbn012.labels.indexOf(_value012)
        val _valueABC = rbnABC.selectedValue
        val _indexABC = rbnABC.labels.indexOf(_valueABC)
        // 選択値に応じた処理
        // ~ここから~
        ...
        // ~ここまで~
    }
}

設定を行うActivity

スポンサーリンク

関連記事:

Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のButtonについて、構成や使用方法などをまとめます。 ※環境:Android Studio Flamingo | 2022.2.1    :androidx.compose.material3:material3:1.1.1    :androidx.compose.ui:ui:1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のSurfaceについて、構成や使用方法などをまとめます。 ※環境:Android Studio Flamingo | 2022.2.1    :androidx.compose.material3:material3:1.1.1    :androidx.compose.ui:ui:1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中にUI要素を格納する機能をもつコンテナ型のUIがあります。 Column/Row/Boxがコンテナ型のUIです。構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のImageについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation 1.5.0 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のDividerについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.material3:material3 1.1.1 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のSpacerについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation 1.5.0 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のSliderについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.material3:material3 1.1.1 ...
スポンサーリンク