【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

この記事からわかること

  • Android Studio/KotlinDatePicker/TimePickerDialog実装方法
  • Widgetsビュー配置する
  • スピナーモード変更方法
  • スピナーモードのカレンダーを非表示にするには?
  • 選択された日時データMainActivity反映させるには?
  • setOnDateChangedListenersetOnTimeChangedListener使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Picker Tools

環境

DatePickerDialogの実装方法

kotlinでButtonをタップした際に日付を入力できるDatePickerDialogを実装していきます。まずはDatePickerDialogFragmentクラスを用意します。ダイアログとして表示させたいのでDialogFragmentを継承して実装していきます。

import android.app.DatePickerDialog
import android.app.Dialog
import android.os.Bundle
import android.widget.DatePicker
import androidx.fragment.app.DialogFragment
import java.util.Calendar

// DialogFragment:onCreateDialogメソッド
// DatePickerDialog.OnDateSetListener:onDateSetメソッドの提供
class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {

    // ダイアログの生成
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // 現在の日付情報を取得
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)

        // 取得した日付情報を元にダイアログを生成
        return DatePickerDialog(requireContext(), this, year, month, day)
    }

    // 日付が選択されたときに呼び出されるコールバックメソッド
    override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {

    }
}

DatePickerDialog.OnDateSetListener

DatePickerDialog.OnDateSetListenerを継承することでDatePickerから日付を選択された時に呼び出されるコールバックメソッド(onDateSet)をオーバーライドできます。

onCreateDialogメソッド内

ダイアログを生成するonCreateDialogメソッド内ではCalendarクラスを使用して現在の日時情報を取得しています。

おすすめ記事:【Kotlin/Android Studio】現在の日付を取得する方法!DateTimeFormatterの使い方

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val c = Calendar.getInstance()
    val year = c.get(Calendar.YEAR)
    val month = c.get(Calendar.MONTH)
    val day = c.get(Calendar.DAY_OF_MONTH)

    return DatePickerDialog(requireContext(), this, year, month, day)
}

これでDatePickerDialogの準備ができたのであとはButtonのクリックイベントに合わせて表示させるだけです。


val button:Button = findViewById(R.id.button)
button.setOnClickListener {
    val dialog = DatePickerFragment()
    dialog.show(supportFragmentManager, "date")
}
【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

TimePickerDialogの実装方法

同じ要領でTimePickerDialogも実装していきます。DatePickerDialogとほぼ同じなので説明は基本的には割愛します。

import android.app.Dialog
import android.app.TimePickerDialog
import android.os.Bundle
import android.widget.TimePicker
import androidx.fragment.app.DialogFragment
import java.util.Calendar

// DialogFragment:onCreateDialogメソッド
// TimePickerDialog.OnTimeSetListener:onTimeSetメソッドの提供
class TimePickerFragment : DialogFragment(), TimePickerDialog.OnTimeSetListener {

    // ダイアログの生成
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // 現在の日付情報を取得
        val c = Calendar.getInstance()
        val hour = c.get(Calendar.HOUR_OF_DAY)
        val minute = c.get(Calendar.MINUTE)

        // 取得した日付情報を元にダイアログを生成
        return TimePickerDialog(activity, this, hour, minute,false)
    }

    // 時間が選択されたときに呼び出されるコールバックメソッド
    override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {

    }
}

TimePickerDialog(activity, this, hour, minute,false)

ここでTimePickerDialogを生成していますが、最後の引数はis24HourViewというパラメータで時間を24時間表示にするか12時間表示にするかのフラグです。

これでTimePickerDialogの準備ができたのであとはButtonのクリックイベントに合わせて表示させるだけです。


val button:Button = findViewById(R.id.button)
button.setOnClickListener {
    val dialog = TimePickerFragment()
    dialog.show(supportFragmentManager, "time")
}
【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

選択された日時データをMainActivityのビューに反映させる

では実際に選択された日時情報をMainActivity側のビューに反映させてみたいと思います。まずはMainActivity側に「日時情報を受け取りTextViewに反映させるメソッド」を追加します。


 // DatePickerFragmentから選択された日付を反映させる
fun updateDate(year: Int, month: Int, day: Int) {
    val dateTextView:TextView = findViewById(R.id.date_text)
    val formattedDate = String.format("%04d-%02d-%02d", year, month + 1, day)
    dateTextView.text = formattedDate
}

続いてDatePickerFragmentクラスの日付が選択された時に呼ばれるonDateSetメソッド内からMainActivity.ktのupdateDateメソッドを呼び出します。(依存性とかは無視してます)


override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
    val activity = activity as MainActivity?
    activity?.updateDate(year, month, day)
}

これで選択した際に日付情報が反映されるようになりました。

【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

interfaceで依存性を軽減する

おすすめ記事:【Kotlin/Android Studio】FragmentのイベントをActivityで受け取る方法

先ほどの例だとDatePickerFragmentクラスがMainActivityクラスの参照を持ってしまうのでinterfaceを利用して依存性を解消していきたいと思います。まずはDatePickerFragmentクラス内にListenerインターフェースを実装しMainActivityに渡したいデータを引数で受け取るメソッドを用意します。さらにonAttachメソッドをオーバーライドしてその中からcontextをListenerへキャストします。


class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {
    interface Listener {
        // MainActivityに渡したいデータを引数で受け取るメソッド
        fun onDataset(year: Int, month: Int, day: Int)
    }

    // DatePickerFragmentクラス内参照用
    private lateinit var listener: Listener

    override fun onAttach(context: Context) {
      super.onAttach(context)
      // listenerプロパティにキャストしてセット
      listener = context as? Listener
          ?: throw ClassCastException("The parent activity needs to implement OnDismissListener")
    }
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
      // val activity = activity as MainActivity?
      // activity?.updateDate(year, month, day)
      listener.onDataset(year,month,day)
    }
}

オーバーライドしているonDateSetメソッド内は上記のように修正します。MainActivityクラス側ではDatePickerFragment.Listenerを継承させ、先ほどのupdateDateメソッドを削除しonDatasetメソッドに入れ替えます。


class MainActivity : AppCompatActivity() ,DatePickerFragment.Listener {

    // 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

    override fun onDataset(year: Int, month: Int, day: Int) {
        val dateTextView:TextView = findViewById(R.id.date_text)
        val formattedDate = String.format("%04d-%02d-%02d", year, month + 1, day)
        dateTextView.text = formattedDate
    }
}

これでMainActivityの参照を持つことなくデータを受け渡すことができました。

Widgetsとして実装する

わざわざダイアログとして実装しなくてもWidgetsとしてビューに配置することも可能です。これによりPickerを自由な位置に配置したり、他のWidgetsと組み合わせたカスタムフラグメントとして実装することができるようになります。

DatePicker

<DatePicker
    android:id="@+id/datePicker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

TimePicker

<TimePicker
    android:id="@+id/timePicker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

スピナーモード

日付や時間を選択するモードとしてスピナーモードがあります。これを実装するにはdatePickerModeortimePickerMode属性にspinnerを渡すだけです。

<DatePicker
    android:id="@+id/datePicker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:datePickerMode="spinner" />

<TimePicker
    android:id="@+id/timePicker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:timePickerMode="spinner" />
【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

スピナーモードのカレンダーを非表示にする

しかしDatePickerではスピナーモードにしてもカレンダーが表示されたままになってしまいます。これを非表示にするにはandroid:calendarViewShown="false"属性を付与します。

<DatePicker
    android:id="@+id/datePicker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:datePickerMode="spinner" 
    android:calendarViewShown="false"/>
【Kotlin/Android Studio】DatePicker/TimePickerDialogの実装方法!

またその他にも背景色の変更や日付選択の範囲など色々カスタマイズできるので詳細は公式サイトを参考にしてください。

公式リファレンス:DatePicker

DatePickerDialogでスピナーモードにする

DatePickerDialog側でスピナーモードにしたい場合はDatePickerDialogの第二引数にandroid.R.style.Theme_Holo_Dialogを渡すことで実装できました。

val dialog = DatePickerDialog(requireContext(), 
    android.R.style.Theme_Holo_Dialog,
    this, 
    year, 
    month, 
    day)

参考文献:Teratail:Kotlin Android 日付や時刻選択をスピナー型で

Widgetsで定義したPickerの変更通知を受け取る

ではPickerを保持するカスタムフラグメントを実装する流れを見ていきます。

  1. カスタムフラグメント用レイアウトの作成
  2. カスタムフラグメント用クラスの作成
  3. イベントリスナーを設定
  4. アクティビティ側から変更通知とデータを受け取り反映

細かいフラグメントの実装方法については以下の記事を参考にしてください。

1.カスタムフラグメント用レイアウトの作成

まずは実際に表示させるFragmentレイアウトを作成していきます。ここでは特別なことはしてませんので割愛します。


<?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:id="@+id/frameLayout2"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:clickable="true"
    tools:context=".CustomDatePickerFragment">

    <DatePicker
        android:id="@+id/datePicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:datePickerMode="spinner"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TimePicker
        android:id="@+id/timePicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:timePickerMode="spinner"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/datePicker" />

    <Button
        android:id="@+id/done_button"
        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/timePicker" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.カスタムフラグメント用クラスの作成

続いて今回の肝となるFragmentを管理するクラスを仕上げていきます。Listenerを利用したMainActivityへのデータの受け渡しはこちらと一緒です。


import android.content.Context
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.DatePicker
import android.widget.TimePicker

class CustomDatePickerFragment : Fragment() {
    interface Listener {
        fun onDataset(year: Int, month: Int, day: Int)
        fun onTimeset(hour: Int, minutes: Int)
    }

    private lateinit var listener: CustomDatePickerFragment.Listener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as? CustomDatePickerFragment.Listener
            ?: throw ClassCastException("The parent activity needs to implement OnDismissListener")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_custom_date_picker, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val doneButton: Button = view.findViewById(R.id.done_button)
        doneButton.setOnClickListener {
            parentFragmentManager.apply {
                popBackStack()
                popBackStack()
            }
        }

        val datePicker: DatePicker = view.findViewById(R.id.datePicker)
        // DatePickerの値が変化したときに呼び出されるリスナーを設定
        datePicker.setOnDateChangedListener { datePicker, year, month, day ->
            listener.onDataset(year,month,day)
        }

        val timePicker: TimePicker = view.findViewById(R.id.timePicker)
        // TimePickerの値が変化したときに呼び出されるリスナーを設定
        timePicker.setOnTimeChangedListener { timePicker, hour, minutes ->
            listener.onTimeset(hour,minutes)
        }
    }
}

3.イベントリスナーを設定

ここで重要なのは以下の部分です。それぞれのWidgetsから呼び出しているsetOnXXXXChangedListenerデータの変更を検知して呼び出されるコールバックメソッドです。この中に変更時に処理させたいことを実装します。

datePicker.setOnDateChangedListener { datePicker, year, month, day ->
    listener.onDataset(year,month,day)
}

timePicker.setOnTimeChangedListener { timePicker, hour, minutes ->
    listener.onTimeset(hour,minutes)
}

4.アクティビティ側から変更通知とデータを受け取り反映

ここも特に変わったことはしてません。

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

class MainActivity : AppCompatActivity() ,CustomDatePickerFragment.Listener{

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

        val button:Button = findViewById(R.id.button)
        button.setOnClickListener {
            supportFragmentManager.beginTransaction().apply {
                add(R.id.main_frame, CustomDatePickerFragment())
                addToBackStack(null)
                commit()
            }
        }
    }
    override fun onDataset(year: Int, month: Int, day: Int) {
        val dateTextView:TextView = findViewById(R.id.date_text)
        val formattedDate = String.format("%04d-%02d-%02d", year, month + 1, day)
        dateTextView.text = formattedDate
    }

    override fun onTimeset(hour: Int, minutes:Int) {
        val timeTextView:TextView = findViewById(R.id.time_text)
        val formattedTime = String.format("%02d:%02d", hour, minutes)
        timeTextView.text = formattedTime
    }
}

これで独自で実装したPickerの値の変化を検知してアクティビティに反映させることができるようになりました。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index