🗓️

Compose Material 3のDatePickerを使ってみる

2023/05/13に公開

Compose Material 3のv1.1.0から導入されたDatePickerとDatePickerDialogを使う機会があったのでそれのまとめです。

https://m3.material.io/components/date-pickers/overview

導入

Gradleファイルに以下を追加します。

dependencies {
    implementation("androidx.compose.material3:material3:1.1.0")
}

DatePicker

実際にDatePickerを使っていきます。
DatePickerは以下のような実装になっていました。

@ExperimentalMaterial3Api
@Composable
fun DatePicker(
    state: DatePickerState,
    modifier: Modifier = Modifier,
    dateFormatter: DatePickerFormatter = remember { DatePickerFormatter() },
    dateValidator: (Long) -> Boolean = { true },
    title: (@Composable () -> Unit)? = {
        DatePickerDefaults.DatePickerTitle(
            state,
            modifier = Modifier.padding(DatePickerTitlePadding)
        )
    },
    headline: (@Composable () -> Unit)? = {
        DatePickerDefaults.DatePickerHeadline(
            state,
            dateFormatter,
            modifier = Modifier.padding(DatePickerHeadlinePadding)
        )
    },
    showModeToggle: Boolean = true,
    colors: DatePickerColors = DatePickerDefaults.colors()
) {
    DateEntryContainer(
        modifier = modifier,
        title = title,
        headline = headline,
        modeToggleButton = if (showModeToggle) {
            {
                DisplayModeToggleButton(
                    modifier = Modifier.padding(DatePickerModeTogglePadding),
                    displayMode = state.displayMode,
                    onDisplayModeChange = { displayMode ->
                        state.stateData.switchDisplayMode(
                            displayMode
                        )
                    },
                )
            }
        } else {
            null
        },
        headlineTextStyle = MaterialTheme.typography.fromToken(
            DatePickerModalTokens.HeaderHeadlineFont
        ),
        headerMinHeight = DatePickerModalTokens.HeaderContainerHeight,
        colors = colors
    ) {
        SwitchableDateEntryContent(
            state = state,
            dateFormatter = dateFormatter,
            dateValidator = dateValidator,
            colors = colors
        )
    }
}

DatePickerStateはmustで渡さないといけないようになっており、このDatePickerStateで選択した日付の状態を持つことになります。

実際に呼び出すときは以下のような感じで呼び出して使用します。

@Composable
fun Material3DatePickerComponent() {
    val datePickerState = rememberDatePickerState(
        initialSelectedDateMillis = Instant.now().toEpochMilli()
    )
    DatePicker(state = datePickerState)
}

rememberDatePickerStateには、初期値を渡すことができるようになっています。
ここでは、現在の時間を渡すようにしています。
rememberDatePickerの内部を見てみると、rememberSaveableが使われており、画面回転などにも対応できるようになっているようです。

@Composable
@ExperimentalMaterial3Api
fun rememberDatePickerState(
    @Suppress("AutoBoxing") initialSelectedDateMillis: Long? = null,
    @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long? = initialSelectedDateMillis,
    yearRange: IntRange = DatePickerDefaults.YearRange,
    initialDisplayMode: DisplayMode = DisplayMode.Picker
): DatePickerState = rememberSaveable(
    saver = DatePickerState.Saver()
) {
    DatePickerState(
        initialSelectedDateMillis = initialSelectedDateMillis,
        initialDisplayedMonthMillis = initialDisplayedMonthMillis,
        yearRange = yearRange,
        initialDisplayMode = initialDisplayMode
    )
}

DatePickerDialog

次にDatePickerDialogを使ってみます。
DatePickerDialogは以下のような感じになっています。

@ExperimentalMaterial3Api
@Composable
fun DatePickerDialog(
    onDismissRequest: () -> Unit,
    confirmButton: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    dismissButton: @Composable (() -> Unit)? = null,
    shape: Shape = DatePickerDefaults.shape,
    tonalElevation: Dp = DatePickerDefaults.TonalElevation,
    colors: DatePickerColors = DatePickerDefaults.colors(),
    properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
    content: @Composable ColumnScope.() -> Unit
) {
    AlertDialog(
        onDismissRequest = onDismissRequest,
        modifier = modifier.wrapContentHeight(),
        properties = properties
    ) {
        Surface(
            modifier = Modifier
                .requiredWidth(DatePickerModalTokens.ContainerWidth)
                .heightIn(max = DatePickerModalTokens.ContainerHeight),
            shape = shape,
            color = colors.containerColor,
            tonalElevation = tonalElevation,
        ) {
            Column(verticalArrangement = Arrangement.SpaceBetween) {
                content()
                // Buttons
                Box(
                    modifier = Modifier
                        .align(Alignment.End)
                        .padding(DialogButtonsPadding)
                ) {
                    CompositionLocalProvider(
                        LocalContentColor provides DialogTokens.ActionLabelTextColor.toColor()
                    ) {
                        val textStyle =
                            MaterialTheme.typography.fromToken(DialogTokens.ActionLabelTextFont)
                        ProvideTextStyle(value = textStyle) {
                            AlertDialogFlowRow(
                                mainAxisSpacing = DialogButtonsMainAxisSpacing,
                                crossAxisSpacing = DialogButtonsCrossAxisSpacing
                            ) {
                                dismissButton?.invoke()
                                confirmButton()
                            }
                        }
                    }
                }
            }
        }
    }
}

DatePickerDialogでは、onDismissRequest・confirmButtonの2つを渡す必要があります。
onDismissRequestは、DatePickerDialog外をタップした時に、どういう挙動をするのかを定義します。
confirmButtonは、DatePickerDialogを表示したときに、ダイアログの下の部分に表示するボタンの挙動を定義します。
実際に使うときは以下のように呼び出します。

@Composable
fun DatePickerComponent() {
    var showPicker by remember { mutableStateOf(false) }
    val datePickerState = rememberDatePickerState(
        initialSelectedDateMillis = Instant.now().toEpochMilli()
    )
    if (showPicker) {
        Material3DatePickerDialogComponent(
            datePickerState = datePickerState,
            closePicker = { showPicker = false },
        )
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Material3DatePickerDialogComponent(
    datePickerState: DatePickerState,
    date: Instant?,
    closePicker: () -> Unit,
    setDate: (Instant) -> Unit,
    modifier: Modifier = Modifier,
) {
    DatePickerDialog(
        onDismissRequest = {
            closePicker()
        },
        confirmButton = {
            TextButton(
                onClick = {
                    datePickerState.setSelection(datePickerState.selectedDateMillis)
                    closePicker()
                }
            ) {
                Text(text = "OK")
            }
        },
        dismissButton = {
            TextButton(
                onClick = {
                    closePicker()
                }
            ) {
                Text(text = "CANCEL")
            }
        },
        modifier = modifier,
    ) {
        DatePicker(state = datePickerState)
    }
}

showPickerでDatePickerDialogを表示するかどうかを判断しています。
confirmButtonでは、datePickerState.setSelection(datePickerState.selectedDateMillis)を使って、選択した日付の状態を保存しています。

参考資料

https://developer.android.com/jetpack/androidx/releases/compose-material3#version_11_2
https://m3.material.io/components/date-pickers/overview
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt;l=153?q=date picker
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt;l=414?q=date picker
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt;l=65?q=date picker Dialog

Discussion