X

Jetpack Compose: Switchコンポーザブルを使う

今回のJetpack ComposeサンプルはSwitchコンポーザブル( androidx.compose.material.switch)です。単一のアイテム、機能の有効/無効やON/OFFを示します。表示例のようにTextコンポーザブルと一緒につかうことが多いでしょう。このスクリーンショットでは有効状態を示してます。

Switchコンポーザブルの使用例

これまでTextそしてButtonコンポーザブルと紹介してきましたが今回は、ついに状態を持つコンポーザブルの登場です。androidx.compose.materialパッケージが提供しているコンポーザブルのなかで状態を表現できるものはSwitch以外に、CheckboxやIconToggleButtonコンポーザブルがあります。

Switchを表示する

Switchコンポーザブル

Switchコンポーザブルの引数ではチェックされているかどうか、つまりON/OFFを示すcheckedと変化を受け取るコールバックonCheckedChangeの2つが必須となっています(その他は省略可能)。

@Composable
fun SwitchSample() {
    val checkedState = remember { mutableStateOf(true) }
    Switch(
        checked = checkedState.value,
        onCheckedChange = { checkedState.value = it }
    )
}

サンプルコードではSwitchの引数checkedに有効/無効の状態を表すcheckedState.valueを設定し、onCheckedChangeでは変更された値itを受け取り、checkedState.valueを新しい値(it)で書き換えています。

この部分はKotlinに慣れていないと不思議な書き方に見えると思うので少しだけ解説しておきます。Switchコンポーザブルの引数、onCheckedChangeの定義はonCheckedChange: ((Boolean) -> Unit)?となっています。これは次のように書き換えられます。

    Switch(
        checked = checkedState.value,
        onCheckedChange = { changedValue ->
              checkedState.value = changedValue }
    )

((Boolean) -> Unit)?のBooleanは、このサンプルのようにchangedValueという名前で受け取ってもいいのですが、冗長であるためKotlin言語では省略できます。省略した場合はitで受け取る、いわゆる糖衣構文(シンタックスシュガー)です。

状態を記憶する

サンプルコードででてきたmutableStateOfはオブザーバブルなデータホルダーです。MutableStateクラスを返却します。

val checkedState = remember { mutableStateOf(true) }

さらにrememberで受け取ってコンポーザブルで状態を覚えておきます。

もうちょっと詳しく(読み飛ばし可)

※ここは読み飛ばしても死にません。詳しく知りたいひとはComposeの公式ガイドライン「Lifecycle of composables」がわかりやすいです。

前述のmutableStateOfは引数で初期化したMutableStateを生成しています。このMutableStateクラスは、ミュータブル(可変)という名前のとおり変更可能です。Composeのシステムによって読み取りと書き込みがオブザーブ(監視)されていますが(詳細は別記事で解説したいと考えているので)ここでは「変更がわかる便利なデータホルダーだな」という理解で大丈夫です。

rememberは引数(calculationとよばれます。このサンプルではMutableStateが渡されています)を覚えてくれる便利なインライン関数です。引数で受け取った値はCompositionでのみ評価されます…というと何をいっているのかわからないかもしれません。Compositionはコンポーザブルのツリー構造を表現する概念で、Composeで記述したUIの変更を検出します。ツリー構造をもっているCompositionは特定のキッカケで再構成するのですが、そのキッカケは再描画が必要なとき、つまりSwitchコンポーザブルなどUIの状態が変わったとき(=MutableStateなどのState<T>の変更)をトリガーとします。

Compositionを再構成(これをRecomposition、再Compositionなどと呼びます)するタイミングを伝える関数がrememberとも言えます。再描画のタイミングを状態変更のタイミングのみに制約することは一見、わかりにくいかもしれませんが、描画の効率性(変更があったUIツリーのみ更新すればいい)かつ、データフローがわかりやすくなるメリットがあります。

閑話休題。

Switchを変更する

Switchコンポーザブルの引数checkedがfalse、つまりなにかがOFFであることを示す場合は次のようにします。

SwitchコンポーザブルをOffにする
@Preview(showBackground = true)
@Composable
fun NonchekedSwitch() {
    Switch(
        checked = false, onCheckedChange = null
    )
}

Switchコンポーザブルが無効のとき

Switchコンポーザブルそのものが操作不能であるなどコンポーザブルが無効である場合はenabled引数をfalseにすれば表現できます。

Switchコンポーザブルが操作できない(操作が無効)を示す
@Preview(showBackground = true)
@Composable
fun DisabledSwitch() {
    Switch(
        checked = true, onCheckedChange = null,
        enabled = false
    )
}

Switchのスタイルを変える

ボタン等と同じく、SwitchコンポーザブルのスタイルはSwitchDefaultsにまとまっています。次の図はチェック状態を赤色に変更したものです。

ONのとき赤色にする例
@Preview(showBackground = true)
@Composable
fun ColoredSwitch() {
    Switch(
        checked = true, onCheckedChange = null,
        colors = SwitchDefaults.colors(
            checkedThumbColor = Color.Red
        )
    )
}

SwitchDefaults.colorsにはcheckedThumbColorをいれて10のスタイル情報が含まれています。enabledとcheckedの組み合わせに応じて色や透明度を指定できるようになっています。

  • checkedThumbColor:enabledかつcheckedでの●部分の色。同様にuncheckedThumbColor、disabledCheckedThumbColor 、disabledUncheckedThumbColorの合計4つがある
  • checkedTrackColor:enabledかつcheckedでの横棒=Trackの色。同様にuncheckedTrackColor、disabledCheckedTrackColor、disabledUncheckedTrackColorの合計4つがある
  • checkedTrackAlpha:checkedTrackColorとdisabledCheckedTrackColorで使う透明度
  • uncheckedTrackAlpha:uncheckedTrackColorとdisabledUncheckedTrackColorで使う透明度

Switchをレイアウトする

マテリアルデザインガイドラインに則ってSwitchコンポーザブルを表示する場合、いろんな実現方法がありますが、手っ取り早くというのであれば次のようなレイアウトが手軽です。

Switchコンポーザブルをレイアウトする

次のようにRowコンポーザブルを使ってTextとSwitchを左右に配置します。このレイアウトのポイントはSpacerコンポーザブルをつかった調整です。

@Preview(showBackground = true)
@Composable
fun SwitchWithText() {
    Row(
        Modifier
            .width(320.dp)
            .padding(horizontal = 16.dp)) {
        Text(
            text = "Jetpack Compose Switch",
            modifier = Modifier
                .align(Alignment.CenterVertically)
                .padding(16.dp),
            style = MaterialTheme.typography.subtitle1
        )
        Spacer(Modifier.weight(1f))

        val checkedState = remember { mutableStateOf(true) }
        Switch(
            checked = checkedState.value,
            onCheckedChange = { checkedState.value = it },
            modifier = Modifier.align(Alignment.CenterVertically)
        )
    }
}

Switchコンポーザブルの引数

@Composable
@OptIn(ExperimentalMaterialApi::class)
fun Switch(
    checked: Boolean,
    onCheckedChange: ((Boolean) -> Unit)?,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    colors: SwitchColors = SwitchDefaults.colors()
) {
  • checked:チェックされているかどうか
  • onCheckedChange:スイッチの変化を受け取るコールバック
  • modifier:レイアウトへ反映するmodifier
  • enabled:スイッチの有効/無効(操作できる/できない)
  • interactionSource:インタラクションに合わせてスタイルや振る舞いを変えたいときはソースとなるストリームを設定する

今回はSwitchコンポーザブルを紹介しました。途中、状態の保存を解説したので少し長くなりましたが、それでもJetpack Composeの根幹をなす部分ですので説明が足りてないところもあります。状態管理の面白さについては技術書典11の書籍化の際や別の記事で触れられれば、と考えています。

Switchコンポーザブルは単体で使うことはまずないため、レイアウトのサンプルもいっしょに紹介しました。ちょっとしたテクニックはJetpack Compose Sampleなど公式のJetpack Composeサンプル集でも見つけることが出来ます。気になるUIがあれば、どのような実装か探してみると面白いはずです。

実務ではSwitchだけでなく冒頭で説明したようなCheckboxやIconToggleButtonコンポーザブルも候補にあがりますので、どこかでこのあたりのコンポーザブルも解説できればと考えています。

今回の記事はこれでおしまいです。お疲れさまでした。

mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org