(育休にてAndroidを久しぶりに触ったので、JetpackComposeを改めて勉強中)
概要
JetpackCompose Material3にて日時設定をしようと思ったら、いまだにDatePickerとTimePickerは別々になっていて面倒だったので、両方を呼び出してDateを取得できるようにする。
(2023/11/27時点だとTimePickerDialogも開発中でサンプルコードがあるのみ)
実装
日付を設定する
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DatePickerDialogComponent(
showPicker: MutableState<Boolean>,
onCanceled: () -> Unit,
onSelected: (dateMillis: Long) -> Unit
) {
if (!showPicker.value) {
return
}
val datePickerState = rememberDatePickerState(
initialSelectedDateMillis = Date().time
)
DatePickerDialog(
onDismissRequest = {
showPicker.value = false
onCanceled()
},
confirmButton = {
TextButton(
onClick = {
val selectedDateMillis = datePickerState.selectedDateMillis
datePickerState.setSelection(selectedDateMillis)
showPicker.value = false
if (selectedDateMillis != null) {
onSelected(selectedDateMillis)
} else {
onCanceled()
}
}
) {
Text(text = "OK")
}
},
dismissButton = {
TextButton(
onClick = {
showPicker.value = false
onCanceled()
}
) {
Text(text = "CANCEL")
}
}
) {
DatePicker(state = datePickerState)
}
}
時刻を設定する
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimePickerDialogComponent(
showPicker: MutableState<Boolean>,
onCanceled: () -> Unit,
onSelected: (hour: Int, minute: Int) -> Unit
) {
if (!showPicker.value) {
return
}
val cal = Calendar.getInstance()
val hour = cal.get(Calendar.HOUR_OF_DAY)
val minute = cal.get(Calendar.MINUTE)
val timePickerState = rememberTimePickerState(
initialHour = hour,
initialMinute = minute,
is24Hour = true
)
TimePickerDialog(
onCancel = {
showPicker.value = false
onCanceled()
},
onConfirm = {
showPicker.value = false
onSelected(timePickerState.hour, timePickerState.minute)
}
) {
TimePicker(state = timePickerState)
}
}
日付と時刻を両方を交互に呼び出す
@Composable
fun DateTimePickerComponent(
showPicker: MutableState<Boolean>,
onSelected: (date: Date) -> Unit
) {
if (!showPicker.value) {
return
}
val showDatePicker = remember { mutableStateOf(true) }
val showTimePicker = remember { mutableStateOf(false) }
val dateMillisState = remember { mutableStateOf<Long?>(null) }
DatePickerDialogComponent(
showPicker = showDatePicker,
onCanceled = {
showPicker.value = false
}
) { dateMillis ->
dateMillisState.value = dateMillis
showTimePicker.value = true
}
TimePickerDialogComponent(
showPicker = showTimePicker,
onCanceled = {
showPicker.value = false
}
) { hour, minute ->
val dateMillis = dateMillisState.value
if (dateMillis != null) {
val cal = Calendar.getInstance()
cal.timeInMillis = dateMillis
cal.set(Calendar.HOUR_OF_DAY, hour)
cal.set(Calendar.MINUTE, minute)
onSelected(cal.time)
}
showPicker.value = false
}
}
使う側
val showPicker = remember { mutableStateOf(false) }
Button(
modifier = onClick = {
showPicker.value = true
}) {
Text(text = "日時指定で記録")
}
DateTimePickerComponent(showPicker = showPicker) {
viewModel.save(it)
}
ひとまずはこれで動きました。
もしかしたらDateではなくInstantなどの新しいAPIを使った方が良さそうですかね。
ただJetpackComposeでのダイアログの操作って、昔のAndroidに慣れてると違和感というか面倒だと思ってしまいます…
追記
DatePickerが以下の初期値だと-9時間されてしまってるのに気づきました。。。
initialSelectedDateMillis = Date().time
ドキュメント見るとUTCで作らないといけなさそうですね
initialSelectedDateMillis - timestamp in UTC milliseconds from the epoch that represents an initial selection of a date. Provide a null to indicate no selection. Note that the state's selectedDateMillis will provide a timestamp that represents the start of the day, which may be different than the provided initialSelectedDateMillis.
こちらで作り直したらイケました。
initialSelectedDateMillis = LocalDate.now()
.atStartOfDay()
.toInstant(ZoneOffset.UTC)
.toEpochMilli()