DatePickerDialog と TimePickerDialog を使って日時選択をできるようにする

時間選択を実装するのに便利な DatePickerDialog と TimePickerDialog に出会ったので備忘録を残そうと思います。

# DatePickerDialog と TimePickerDialog とは
実態としては AlertDialog の setView に カスタムのレイアウトを追加しているものです。
DatePickerDialog であれば DatePicker を、TimePickerDialog であれば TimePicker を追加しています。

さらに PositiveButton と NegativeButton はデフォで実装されているので、 show してあげるだけで簡単に時間選択用のダイアログを表示することができます。

参考イメージ
DatePickerDialog
![TimePickerSample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236467/0793c375-1d6c-52d8-4069-9537e6557084.png)

TimePickerDialog
![DatePickerSample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236467/7e3befcd-c78e-3d7f-5baf-0bf43cd31a87.png)


#手順
今回主に使用するクラスは以下となります。

- DialogFragment
- DatePickerDialog
- TimePickerDialog 

実装の手順としては、
1. DialogFragment を継承したクラスを作成。
2. DatePickerDialog/TimePickerDialog の Listener を DialogFragment を継承したクラスに実装。
3. onCreateDialog で DatePickerDialog/TimePickerDialog をリターン
4. 表示したいクラスで DialogFragment の show

今回は DatePickerDialog/TimePickerDialog で選択した日時をTextView に表示するものを作成したいと思います。

#実装
まずはレイアウトです。
ボタンタップ後にダイアログを表示したいのでボタンを2つ設置し、日にちと時間の選択結果をそれぞれ分けて表示するための TextView を設置しています。

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".dialog.DialogSampleActivity">

    <TextView
        android:id="@+id/dateTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="日にちが入ります"
        app:layout_constraintBottom_toTopOf="@+id/showDateDialogButton"
        app:layout_constraintTop_toTopOf="parent"
        tools:layout_editor_absoluteX="0dp" />

    <Button
        android:id="@+id/showDateDialogButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="日にち設定"
        app:layout_constraintBottom_toTopOf="@+id/timeTextView"
        app:layout_constraintEnd_toEndOf="@+id/showTimeDialogButton"
        app:layout_constraintStart_toStartOf="@+id/showTimeDialogButton"
        app:layout_constraintTop_toBottomOf="@+id/dateTextView" />

    <Button
        android:id="@+id/showTimeDialogButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="時間設定"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/timeTextView" />

    <TextView
        android:id="@+id/timeTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="時間が入ります"
        app:layout_constraintBottom_toTopOf="@+id/showTimeDialogButton"
        app:layout_constraintTop_toBottomOf="@+id/showDateDialogButton"
        tools:layout_editor_absoluteX="0dp" />
    
</androidx.constraintlayout.widget.ConstraintLayout>
```


続いて DatePickerDialog と TimePickerDialog を表示するためのDialogFragment を作成します。
やっていることはほとんど同じで、DatePickerDialog/TimePickerDialog から選択結果をもらい、 Activity/Fragment に渡すということを行っています。

```kotlin:DatePickerDialogFragment.kt
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.widget.DatePicker
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import java.util.*

class DatePickerDialogFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {
    // Activity や Fragment に選択結果を渡すためのリスナー
    interface OnSelectedDateListener {
        fun selectedDate(year: Int, month: Int, date: Int)
    }

    private lateinit var listener: OnSelectedDateListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnSelectedDateListener) {
            listener = context
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val calendar = Calendar.getInstance()
        calendar.timeInMillis = Date().time
        val context = context // smart cast
        return when {
            context != null -> {
                DatePickerDialog(
                    context,
                    this, // ここでは DatePickerDialog の リスナーを渡す
                    calendar.get(Calendar.YEAR),
                    calendar.get(Calendar.MONTH),
                    calendar.get(Calendar.DATE))
            }
            else -> super.onCreateDialog(savedInstanceState)
        }
    }

    // DatePickerDialog から選択結果が渡される。
    override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
        listener.selectedDate(year, month, dayOfMonth)
    }

    companion object {
        private val TAG = DatePickerDialogFragment::class.java.simpleName
    }
}
```

```kotlin:TimePickerDialogFragment.kt
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.Context
import android.os.Bundle
import android.widget.TimePicker
import androidx.fragment.app.DialogFragment
import java.util.*

class TimePickerDialogFragment : DialogFragment(), TimePickerDialog.OnTimeSetListener {
    // Activity や Fragment に選択結果を渡すためのリスナー
    interface OnSelectedTimeListener {
        fun selectedTime(hour: Int, minute: Int)
    }

    private lateinit var listener: OnSelectedTimeListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnSelectedTimeListener) {
            listener = context
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val calendar = Calendar.getInstance()
        calendar.timeInMillis = Date().time
        val context = context // smart cast
        return when {
            context != null -> {
                TimePickerDialog(
                    context,
                    this, // ここでは TimePickerDialog の リスナーを渡す
                    calendar.get(Calendar.HOUR_OF_DAY),
                    calendar.get(Calendar.MINUTE),
                    true)
            }
            else -> super.onCreateDialog(savedInstanceState)
        }
    }
    // TimePickerDialog から選択結果が渡される。
    override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
        listener.selectedTime(hourOfDay, minute)
    }

    companion object {
        @Suppress("unused")
        private val TAG = TimePickerDialogFragment::class.java.simpleName
    }
}
```

今回は Activity で表示しているので Activity のコードです。
DatePickerDialogFragmet と TimePickerDialogFragment から選択した日時を取得し TextView に表示しています。

```kotlin:DialogSampleActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.workmanagersample.R
import kotlinx.android.synthetic.main.activity_dialog_sample.*
import kotlinx.android.synthetic.main.activity_local_notification.dateTextView
import kotlinx.android.synthetic.main.activity_local_notification.showDateDialogButton
import kotlinx.android.synthetic.main.activity_local_notification.showTimeDialogButton

class DialogSampleActivity :
    AppCompatActivity(),
    DatePickerDialogFragment.OnSelectedDateListener,
    TimePickerDialogFragment.OnSelectedTimeListener
{

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dialog_sample)

        showDateDialogButton.setOnClickListener {
            showDatePickerDialog()
        }

        showTimeDialogButton.setOnClickListener {
            showTimePickerDialog()
        }
    }

    private fun showDatePickerDialog() {
        val datePickerDialogFragment = DatePickerDialogFragment()
        datePickerDialogFragment.show(supportFragmentManager, null)
    }

    override fun selectedDate(year: Int, month: Int, date: Int) {
        dateTextView.text = "${year}年${month}月${date}日"
    }

    private fun showTimePickerDialog() {
        val timePickerDialogFragment = TimePickerDialogFragment()
        timePickerDialogFragment.show(supportFragmentManager, null)
    }

    override fun selectedTime(hour: Int, minute: Int) {
        timeTextView.text = "${hour}時${minute}分"
    }
}
```

ここまでで行うと以下のようになっていると思います。
![DateTimeSAmpleGif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236467/2ff6ae19-b9e4-9d77-8931-049c92a01c19.gif)

#最後に
DatePickerDialog と TimePickerDialog を知る前は自前で作成した Layout を AlertDialog にセットして作ろうと思っていたのですが、ちょっと調べると自分のやりたいことが簡単に実現できるものがすでに存在するので、最初にちゃんと調べる癖を付けたいと思いました。
全然関係ないのですが、TimePicker で時間を間違えてしまった時にどうやって時間のところまで戻るのか分からずに困惑してしまいました（時間の選択をすると分に行ってしまい、戻るボタンも無いので、どうやって戻るのか分からなかったです。。)
みていただきありがとうございました(これから少し仮眠をとりまs( ˘ω˘)ｽﾔｧ💤)