Android の DatePickerDialog で日時範囲(開始日時～終了日時)の選択

#目的
### Android の DatePickerDialog を使って、日付範囲(開始日～終了日)を選択させる。
- DatePickerDialogで日付を選んで、Buttonのtextに表示させる。
    - Buttonじゃなくて他のViewに表示させてもOK。TextViewとか。
- 選択できる日付を制限する。
    - 開始日に終了日より後の日付を選択させない。
    - 終了日に開始日より前の日付を選択させない。

### その他の仕様(詳しい説明は[こちらの記事](https://qiita.com/curry_rohio/items/30c24ad023f9984cf8ca)に書いてます)

すでに日付が選択されており、DatePickerDialogで日付を選ばなかったとき、選択された日付を削除する。

- キャンセルを押したときにButton.textにセットされていた値を変更するということ。
- ここでは日付が未入力の場合、"指定なし"というテキストをセットすることにします。


# 動作環境
- Kotlin 1.3.21
- Android 8.0 (Oreo)
- AndroidStudio 3.3.2

# 動作イメージ
![dataRangeTest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398440/e085cead-e662-63fd-b7a4-249ff257d3a6.gif)

# 実装
## layoutファイル
開始日付と終了日付を選択・表示するButtonとTextViewの構成とします。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:context=".MainActivity">
    <TextView
            android:text="日付範囲"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/date_range_text"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
            android:layout_marginLeft="8dp" android:layout_marginStart="8dp" android:layout_marginTop="16dp"/>
    <Button
            android:text="指定なし"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/begin_date_button"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/date_range_text" android:layout_marginLeft="64dp"
            android:layout_marginStart="64dp" android:layout_marginTop="16dp"/>
    <TextView
            android:text="~"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/range"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/begin_date_button" android:layout_marginLeft="8dp"
            android:layout_marginStart="8dp" android:layout_marginTop="16dp"/>
    <Button
            android:text="指定なし"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/end_date_button"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/range" android:layout_marginLeft="8dp"
            android:layout_marginStart="8dp" android:layout_marginTop="16dp"/>
</android.support.constraint.ConstraintLayout>
```


## activityファイル
以下は、MainActivity.onCreateメソッド内のコードですが、分かりやすくするために2つに分けます。

- **dateSetOnClickListener.kt** : ButtonのViewを取得とButton.setOnClickListenerの実装を行う。
- **DateDialogFragment.kt** : DateDialogFragmentを表示させるclass。

```kotlin:dateSetOnClickListener.kt
val mBeginDate = findViewById<Button>(R.id.begin_date_button)
val mEndDate = findViewById<Button>(R.id.end_date_button)

/* 開始日付を指定するボタンを押したとき */
mBeginDate.setOnClickListener {
//    DialogFragmentを生成し、表示
    DateDialogFragment(mBeginDate).show(supportFragmentManager, mBeginDate::class.java.simpleName)
}
/* 終了日付を指定するボタンを押したとき */
mEndDate.setOnClickListener {
    DateDialogFragment(mEndDate).show(supportFragmentManager, mEndDate::class.java.simpleName)
}
```

```kotlin:DateDialogFragment.kt
/* 日付を入力する時に使用するinnerクラス
   DialogFragmentで日付を入力できるカレンダーを表示する
   DialogFragmentを呼び出したボタンを取得したいので、コンストラクタの引数にButtonを定義する */
class DateDialogFragment(val button: Button) : DialogFragment() {
    /* DatePickerDialogを返却するメソッド
       このメソッドで、日付を選択した後の処理や、日付範囲、Dialogのタイトルを設定する */
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
//        現在日付を取得
        val calendar = Calendar.getInstance()
        return DatePickerDialog(
            context,
            theme,
//            DialogFragmentで日付を選択し、OKを押したときの処理
            DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
                val inputDate = Calendar.getInstance()
//                選択された日付を取得
                inputDate.set(year, month, dayOfMonth)
                val dfInputeDate = SimpleDateFormat("yyyy-MM-dd", Locale.US)
//                CalendarからStringへ変換
                val strInputDate = dfInputeDate.format(inputDate.time)
//                DialogFragmentを呼び出したボタンのテキストに日付をセット
                button.text = strInputDate
            },
            calendar.get(Calendar.YEAR),
            calendar.get(Calendar.MONTH),
            calendar.get(Calendar.DAY_OF_MONTH))
            .also {
                /* 選択可能な日付の上限を設定
                   終了日付が指定されている場合、開始日付は終了日付より後を選べないようにする */
                if (button === mBeginDate && mEndDate.text != "指定なし"){
                    val maxDate = Calendar.getInstance()
                    val dfMaxDate = SimpleDateFormat("yyyy-MM-dd", Locale.JAPAN)
//                    指定されている終了日付を取得
                    val endInputDate = dfMaxDate.parse(mEndDate.text.toString())
                    maxDate.time = endInputDate
//                    選択可能な開始日付の上限に指定されている終了日付を設定
                    it.datePicker.maxDate = maxDate.timeInMillis
                } else {
//                    選択可能な開始日付の上限に現在日付を設定
                    it.datePicker.maxDate = calendar.timeInMillis
                }
                /*  選択可能な日付の下限を指定 */
                val minDate = Calendar.getInstance()
                /* 開始日付が指定されている場合、終了日付は開始日付より前を選べないようにする */
                if (button === mEndDate && mBeginDate.text != "指定なし"){
                    val dfMinDate = SimpleDateFormat("yyyy-MM-dd", Locale.JAPAN)
                    val beginInputDate = dfMinDate.parse(mBeginDate.text.toString())
                    minDate.time = beginInputDate
//                    選択可能な終了日付の下限に指定されている開始日付を設定
                    it.datePicker.minDate = minDate.timeInMillis
                } else {
//                    選択可能な終了日付の下限を設定、ここでは2018/2/1とします
                    minDate.set(2018, 2, 1)
                    it.datePicker.minDate = minDate.timeInMillis
                }
//                タイトルが勝手に表示されるのを防ぐために空文字をセット
                it.setTitle("")
            }
    }

    /* DialogFragmentでcancelを押した際の処理 */
    override fun onCancel(dialog: DialogInterface?) {
        super.onCancel(dialog)
//        cancelを押した際はセットされていた日付を削除し、"指定なし"をセット
        button.text = "指定なし"
    }
}
```