【Android】初心者向け シンプルなドラムロール式TimePickerサンプル

以前にドラムロール式のDatePickerのサンプルを紹介しました。
https://qiita.com/masayah/items/c979e42b6333f15bd930

今回時刻入力が必要になったので時刻用のTimePickeを作成しました。サンプルとしてご紹介します。
私と同じようなAndroid開発初心者の方の参考になれば幸いです。

#ドラムロール式TimePickerのイメージ
![無題2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546646/ac71483c-4482-ed64-5a95-ec89892ccba7.png)

#時刻入力でドラムロール式の必要があるのか？
最近のAndroidの標準の時刻入力方式は私も気に入ってます。こういう↓やつですね。
![無題3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546646/80bf3402-0a53-e861-8494-43b2b018a13a.png)
ただ、利用者によってはドラムロール式のほうが馴染みが深い場合もあると思います。
使い分けですよね。

#サンプルの仕様
・時刻入力欄は直接時刻を入力することもできる。
・入力欄が空白など時刻として認識できない場合、初期値として12:00がTimePickerに表示される。
・入力欄が時刻の場合、TimePickerには入力欄の時刻が初期表示される。
・TimePickerで選択した時刻が入力欄に反映される。

#環境
android studio BumbleBee 2021.1.1
compileSdk 32

#コード
さっそくコードの主要部分を紹介します。
ここではわかり易さを重視して必要な部分のコードのみ抜粋しています。
ソース全体は https://github.com/masayahak/TimePicker へアップしています。

# TimePickerを呼び出すアクティビティ
```xml:activity_main.xml
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/time_label"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="48dp"
        android:hint="予定時刻"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/time"
            android:layout_width="220dp"
            android:layout_height="60dp"
            android:inputType="time"
            android:clickable="false"
            />
    </com.google.android.material.textfield.TextInputLayout>

    <!-- TimePicker表示ボタン -->
    <ImageButton
        android:id="@+id/time_picker_actions"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:contentDescription="TimePicker"
        android:src="@drawable/ic_arrow_down"
        app:layout_constraintTop_toTopOf="@id/time_label"
        app:layout_constraintBottom_toBottomOf="@id/time_label"
        android:layout_marginTop="4dp"
        app:layout_constraintEnd_toEndOf="@id/time_label"
        android:layout_marginEnd="8dp"
        app:tint="@android:color/black"
        android:background="@color/white"
        />
```
時刻入力欄としてTextInputEditTextを利用し、android:clickable="false"にしました。
TimePickerダイアログを表示するのはImageButtonが担当しています。
ImageButtonのandroid:src="@drawable/ic_arrow_down"は下向きの矢印アイコンです。
クリックでTimePickerダイアログが表示されることを明示するためのアイコンです。

```kotlin:MainActivity.kt
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val img = findViewById<ImageView>(R.id.time_picker_actions)
        img.setOnClickListener {
            // ドラムロール式TimePickerを呼び出す
            val eText = findViewById<View>(R.id.time) as EditText
            MyTimePicker.showTimePicker(eText)
        }
    }
```
MyTimePicker.showTimePickerで汎用化したTimePickerダイアログを呼び出しています。
引数へTimePickerが値を受け渡しする時刻入力用のEditTextを渡してます。

# 汎用化したTimePicker
```kotlin:MyTimePicker.kt
@RequiresApi(Build.VERSION_CODES.O)
object MyTimePicker {

    fun showTimePicker (eText : EditText) {

        // 時刻入力フィールドが空白など日付ではないときの初期値として、12時を設定
        var hourOfDay = 12
        var minutes = 0

        // 時刻入力フィールドの値が時刻の場合はその値を設定
        val ldt = toLocaleTime(eText.text.toString())
        if (ldt != null) {
            hourOfDay = ldt.hour
            minutes = ldt.minute
        }

        // ドラム式TimePicker表示
        val picker = TimePickerDialog(
            eText.rootView.context,

            // ↓この行をコメントアウトすると標準の時刻入力ダイアログになります。
            AlertDialog.THEME_HOLO_LIGHT,   // テーマ：ドラム式 背景白

            // ダイアログでOKをクリックされたときの処理 時刻入力フィールドへ値を設定
            { _, getHour, getMinutes
                -> eText.setText(String.format("%02d:%02d",
                getHour,
                getMinutes))
            },

            // TimePickerが初期表示する時刻
            hourOfDay,
            minutes,

            // 将来選択できるようにするかもだけど、とりあえず24時間表記
            true
        )
        picker.show()

    }

    private const val TIME_FORMAT = "yyyy/MM/dd HH:mm"

    fun toLocaleTime(stringTime : String) : LocalDateTime? {
        val df = DateTimeFormatter.ofPattern(TIME_FORMAT)
        // 時刻だけだとLocalDateTimeにうまく変換できないので、仮で2001年1月1日にしてる。
        return try { LocalDateTime.parse("2001/01/01 " + stringTime, df) } catch (t: Throwable) { null }
    }

}
```

#終わりに
非常に短いコードですが、１つずつ調べながら手作りするのは大変ですし、完全に動くフルセットのサンプルがあると助かるケースが多いですよね。お役に立てれば幸いです。
コメント、質問も歓迎です。