Android(Kotlin)日付・時間選択ダイアログ
September 03, 2020
はじめに
ユーザーに日付、時間を選択させたい場合、DatePickerDialog と TimePickerDialog を利用するのが良いと思います。ただ、ドキュメントにもそれぞれの使い方が記載されてはいるんですが、実際に使えるレベルまで実装するとプラスアルファで色々あったので、備忘録として残しておきます。
実装内容
概要
DatePickerDialog
を内部的に利用するDatePickerDialogFragment.kt
TimePickerDialog
を内部的に利用するTimePickerDialogFragment.kt
- それぞれを利用する処理のみ実装で
FormFragment.kt
詳細
DatePickerDialogFragment.kt
常に現在日時が初期値となっているダイアログは使えないので、初期値を与えることができるようにインスタンス化メソッドを追加しています。
また、フラグメントから呼び出されることを想定した作りになっていますが、アクティビティから呼び出す場合は onAttach()
を修正する必要があります。
ついでに、DatePickerDialog
は java.util.Calendar
の月数(実数 - 1)を前提に実装されていますが、使う側にそんな特有なルールを強制するのはイマイチなので(LocalDate
使いたいし)対応しています。
package com.example.myapplication.ui.datetime
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.widget.DatePicker
import androidx.fragment.app.DialogFragment
import java.util.*
class DatePickerDialogFragment private constructor() : DialogFragment(), DatePickerDialog.OnDateSetListener {
interface DatePickerDialogListener {
fun onDateSet(year: Int, month: Int, dayOfMonth: Int)
}
companion object {
private const val ARG_YEAR = "ARG_YEAR"
private const val ARG_MONTH = "ARG_MONTH"
private const val ARG_DAY_OF_MONTH = "ARG_DAY_OF_MONTH"
@JvmStatic
fun newInstance(year: Int, month: Int, dayOfMonth: Int): DatePickerDialogFragment {
return DatePickerDialogFragment().apply {
arguments = Bundle().apply {
putInt(ARG_YEAR, year)
putInt(ARG_MONTH, month)
putInt(ARG_DAY_OF_MONTH, dayOfMonth)
}
}
}
@JvmStatic
fun newInstance(): DatePickerDialogFragment {
return DatePickerDialogFragment()
}
}
private lateinit var listener: DatePickerDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
when (parentFragment) {
is DatePickerDialogListener -> listener = parentFragment as DatePickerDialogListener
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val calendar = Calendar.getInstance()
var year = calendar.get(Calendar.YEAR)
var month = calendar.get(Calendar.MONTH)
var dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
if (arguments != null) {
year = requireArguments().getInt(ARG_YEAR)
month = requireArguments().getInt(ARG_MONTH) - 1
dayOfMonth = requireArguments().getInt(ARG_DAY_OF_MONTH)
}
return DatePickerDialog(requireContext(), this, year, month, dayOfMonth)
}
override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) {
listener.onDateSet(year, month + 1, dayOfMonth)
}
}
TimePickerDialogFragment.kt
こちらもインスタンス化メソッド追加、フラグメントから呼び出し前提なのは、前述の DatePickerDialogFragment.kt
と同様です。
package com.example.myapplication.ui.datetime
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.Context
import android.os.Bundle
import android.text.format.DateFormat
import android.widget.TimePicker
import androidx.fragment.app.DialogFragment
import java.util.*
class TimePickerDialogFragment private constructor() : DialogFragment(), TimePickerDialog.OnTimeSetListener {
interface TimePickerDialogListener {
fun onTimeSet(hourOfDay: Int, minute: Int)
}
companion object {
private const val ARG_HOUR_OF_DAY = "ARG_HOUR_OF_DAY"
private const val ARG_MINUTE = "ARG_MINUTE"
@JvmStatic
fun newInstance(hourOfDay: Int, minute: Int): TimePickerDialogFragment {
return TimePickerDialogFragment().apply {
arguments = Bundle().apply {
putInt(ARG_HOUR_OF_DAY, hourOfDay)
putInt(ARG_MINUTE, minute)
}
}
}
@JvmStatic
fun newInstance(): TimePickerDialogFragment {
return TimePickerDialogFragment()
}
}
private lateinit var listener: TimePickerDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
when (parentFragment) {
is TimePickerDialogListener -> listener = parentFragment as TimePickerDialogListener
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val calendar = Calendar.getInstance()
var hour = calendar.get(Calendar.HOUR_OF_DAY)
var minute = calendar.get(Calendar.MINUTE)
if (arguments != null) {
hour = requireArguments().getInt(ARG_HOUR_OF_DAY)
minute = requireArguments().getInt(ARG_MINUTE)
}
return TimePickerDialog(requireContext(), this, hour, minute, DateFormat.is24HourFormat(activity))
}
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
listener.onTimeSet(hourOfDay, minute)
}
}
FormFragment.kt
今回の話と無関係な処理は省いて記載しています。
また、詳細は割愛しますが、LocalDate
と LocalTime
はデフォルトで使えないことには注意です。
class FormFragment : Fragment(),
DatePickerDialogFragment.DatePickerDialogListener, TimePickerDialogFragment.TimePickerDialogListener {
private lateinit var dateEditText: EditText
private lateinit var timeEditText: EditText
private var date: LocalDate? = null
private var time: LocalTime? = null
fun showDatePickerDialog() {
var fragment: DatePickerDialogFragment? = null
if (date != null) {
fragment = DatePickerDialogFragment.newInstance(date!!.year, date!!.monthValue, date!!.dayOfMonth)
}
if (fragment == null) {
fragment = DatePickerDialogFragment.newInstance()
}
fragment.show(childFragmentManager, "DatePickerDialogFragment")
}
fun showTimePickerDialog() {
var fragment: TimePickerDialogFragment? = null
if (time != null) {
fragment = TimePickerDialogFragment.newInstance(time!!.hour, time!!.minute)
}
if (fragment == null) {
fragment = TimePickerDialogFragment.newInstance()
}
fragment.show(childFragmentManager, "TimePickerDialogFragment")
}
override fun onDateSet(year: Int, month: Int, dayOfMonth: Int) {
date = LocalDate.of(year, month, dayOfMonth)
dateEditText.setText("${"%04d".format(year)}/${"%02d".format(month)}/${"%02d".format(dayOfMonth)}")
}
override fun onTimeSet(hourOfDay: Int, minute: Int) {
time = LocalTime.of(hourOfDay, minute)
timeEditText.setText("${"%02d".format(hourOfDay)}:${"%02d".format(minute)}")
}
}