florent37 / SingleDateAndTimePicker

You can now select a date and a time with only one widget !
Apache License 2.0
1.02k stars 334 forks source link

Can I change the order? For example, year-month-day #161

Closed mofada closed 5 years ago

agustinsivoplas commented 5 years ago

Do you have a solution for this?

mofada commented 5 years ago

No, I don't, I use other .

agustinsivoplas commented 5 years ago

I could achieve. I have created my own version of time picker with my layout but using library components. In my case I need month-day-year.

import android.content.Context
import android.text.format.DateFormat
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import com.github.florent37.singledateandtimepicker.DateHelper
import com.github.florent37.singledateandtimepicker.widget.WheelAmPmPicker
import com.github.florent37.singledateandtimepicker.widget.WheelDayOfMonthPicker
import com.github.florent37.singledateandtimepicker.widget.WheelDayPicker
import com.github.florent37.singledateandtimepicker.widget.WheelHourPicker
import com.github.florent37.singledateandtimepicker.widget.WheelMinutePicker
import com.github.florent37.singledateandtimepicker.widget.WheelMonthPicker
import com.github.florent37.singledateandtimepicker.widget.WheelPicker
import com.github.florent37.singledateandtimepicker.widget.WheelYearPicker
import org.allinahealth.cv.R
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Calendar
import java.util.Date
import java.util.Locale

class CustomSingleDateAndTimePicker : LinearLayout {

    companion object {
        const val IS_CYCLIC_DEFAULT = true
        const val IS_CURVED_DEFAULT = false
        const val MUST_BE_ON_FUTUR_DEFAULT = false
        const val DELAY_BEFORE_CHECK_PAST = 200
        const val VISIBLE_ITEM_COUNT_DEFAULT = 7
        const val PM_HOUR_ADDITION = 12
        const val FORMAT_24_HOUR = "EEE d MMM H:mm"
        const val FORMAT_12_HOUR = "EEE d MMM h:mm a"
    }

    private var yearsPicker: WheelYearPicker
    private var monthPicker: WheelMonthPicker
    private var daysOfMonthPicker: WheelDayOfMonthPicker
    private var daysPicker: WheelDayPicker
    private var minutesPicker: WheelMinutePicker
    private var hoursPicker: WheelHourPicker
    private var amPmPicker: WheelAmPmPicker

    private val pickers = ArrayList<WheelPicker<*>>()

    private val listeners = ArrayList<OnDateChangedListener>()

    private var dtSelector: View
    private var mustBeOnFuture: Boolean = false

    private var minDate: Date? = null
    private var maxDate: Date? = null
    private var defaultDate: Date

    private var displayYears = false
    private var displayMonth = false
    private var displayDaysOfMonth = false
    private var displayDays = true
    private var displayMinutes = true
    private var displayHours = true

    private var isAmPm: Boolean = false

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        defaultDate = Date()
        isAmPm = !DateFormat.is24HourFormat(context)

        View.inflate(context, R.layout.single_day_picker, this)

        yearsPicker = findViewById(R.id.yearPicker)
        monthPicker = findViewById(R.id.monthPicker)
        daysOfMonthPicker = findViewById(R.id.daysOfMonthPicker)
        daysPicker = findViewById(R.id.daysPicker)
        minutesPicker = findViewById(R.id.minutesPicker)
        hoursPicker = findViewById(R.id.hoursPicker)
        amPmPicker = findViewById(R.id.amPmPicker)
        dtSelector = findViewById(R.id.dtSelector)

        pickers.addAll(listOf(daysPicker, minutesPicker, hoursPicker, amPmPicker, daysOfMonthPicker, monthPicker, yearsPicker))

        init(context, attrs)
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()

        yearsPicker.setOnYearSelectedListener { picker, position, year ->
            updateListener()
            checkMinMaxDate(picker)

            if (displayDaysOfMonth) {
                updateDaysOfMonth()
            }
        }

        monthPicker.setOnMonthSelectedListener { picker, monthIndex, monthName ->
            updateListener()
            checkMinMaxDate(picker)

            if (displayDaysOfMonth) {
                updateDaysOfMonth()
            }
        }

        daysOfMonthPicker
                .setDayOfMonthSelectedListener { picker, dayIndex ->
                    updateListener()
                    checkMinMaxDate(picker)
                }

        daysOfMonthPicker
                .setOnFinishedLoopListener {
                    if (displayMonth) {
                        monthPicker.scrollTo(monthPicker.currentItemPosition + 1)
                        updateDaysOfMonth()
                    }
                }

        daysPicker
                .setOnDaySelectedListener { picker, position, name, date ->
                    updateListener()
                    checkMinMaxDate(picker)
                }

        minutesPicker
                .setOnMinuteChangedListener { picker, minutes ->
                    updateListener()
                    checkMinMaxDate(picker)
                }
                .setOnFinishedLoopListener { hoursPicker.scrollTo(hoursPicker.currentItemPosition + 1) }

        hoursPicker
                .setOnFinishedLoopListener { daysPicker.scrollTo(daysPicker.currentItemPosition + 1) }
                .setHourChangedListener { picker, hour ->
                    updateListener()
                    checkMinMaxDate(picker)
                }

        amPmPicker
                .setAmPmListener { picker, isAm ->
                    updateListener()
                    checkMinMaxDate(picker)
                }

        setDefaultDate(this.defaultDate) // update displayed date
    }

    override fun setEnabled(enabled: Boolean) {
        super.setEnabled(enabled)
        for (picker in pickers) {
            picker.isEnabled = enabled
        }
    }

    fun setDisplayYears(displayYears: Boolean) {
        this.displayYears = displayYears
        yearsPicker.visibility = if (displayYears) View.VISIBLE else View.GONE
    }

    fun setDisplayMonths(displayMonths: Boolean) {
        this.displayMonth = displayMonths
        monthPicker.visibility = if (displayMonths) View.VISIBLE else View.GONE
        checkSettings()
    }

    fun setDisplayDaysOfMonth(displayDaysOfMonth: Boolean) {
        this.displayDaysOfMonth = displayDaysOfMonth
        daysOfMonthPicker.visibility = if (displayDaysOfMonth) View.VISIBLE else View.GONE

        if (displayDaysOfMonth) {
            updateDaysOfMonth()
        }
        checkSettings()
    }

    fun setDisplayDays(displayDays: Boolean) {
        this.displayDays = displayDays
        daysPicker.visibility = if (displayDays) View.VISIBLE else View.GONE
        checkSettings()
    }

    fun setDisplayMinutes(displayMinutes: Boolean) {
        this.displayMinutes = displayMinutes
        minutesPicker.visibility = if (displayMinutes) View.VISIBLE else View.GONE
    }

    fun setDisplayHours(displayHours: Boolean) {
        this.displayHours = displayHours
        hoursPicker.visibility = if (displayHours) View.VISIBLE else View.GONE

        setIsAmPm(this.isAmPm)
        hoursPicker.setIsAmPm(isAmPm)
    }

    fun setDisplayMonthNumbers(displayMonthNumbers: Boolean) {
        this.monthPicker.setDisplayMonthNumbers(displayMonthNumbers)
        this.monthPicker.updateAdapter()
    }

    fun setTodayText(todayText: String?) {
        if (todayText != null && !todayText.isEmpty()) {
            daysPicker.setTodayText(todayText)
        }
    }

    fun setCurved(curved: Boolean) {
        for (picker in pickers) {
            picker.isCurved = curved
        }
    }

    fun setCyclic(cyclic: Boolean) {
        for (picker in pickers) {
            picker.isCyclic = cyclic
        }
    }

    fun setTextSize(textSize: Int) {
        for (picker in pickers) {
            picker.itemTextSize = textSize
        }
    }

    fun setSelectedTextColor(selectedTextColor: Int) {
        for (picker in pickers) {
            picker.selectedItemTextColor = selectedTextColor
        }
    }

    fun setTextColor(textColor: Int) {
        for (picker in pickers) {
            picker.itemTextColor = textColor
        }
    }

    fun setSelectorColor(selectorColor: Int) {
        dtSelector.setBackgroundColor(selectorColor)
    }

    fun setSelectorHeight(selectorHeight: Int) {
        val dtSelectorLayoutParams = dtSelector.layoutParams
        dtSelectorLayoutParams.height = selectorHeight
        dtSelector.layoutParams = dtSelectorLayoutParams
    }

    fun setVisibleItemCount(visibleItemCount: Int) {
        for (picker in pickers) {
            picker.visibleItemCount = visibleItemCount
        }
    }

    fun setIsAmPm(isAmPm: Boolean) {
        this.isAmPm = isAmPm

        amPmPicker.visibility = if (isAmPm && displayHours) View.VISIBLE else View.GONE
        hoursPicker.setIsAmPm(isAmPm)
    }

    fun isAmPm(): Boolean {
        return isAmPm
    }

    fun setDayFormatter(simpleDateFormat: SimpleDateFormat?) {
        if (simpleDateFormat != null) {
            this.daysPicker.setDayFormatter(simpleDateFormat)
        }
    }

    fun getMinDate(): Date? {
        return minDate
    }

    fun setMinDate(minDate: Date) {
        this.minDate = minDate
        setMinYear()
    }

    fun getMaxDate(): Date? {
        return maxDate
    }

    fun setMaxDate(maxDate: Date) {
        this.maxDate = maxDate
        setMinYear()
    }

    fun setCustomLocale(locale: Locale) {
        for (p in pickers) {
            p.setCustomLocale(locale)
            p.updateAdapter()
        }
    }

    private fun checkMinMaxDate(picker: WheelPicker<*>) {
        checkBeforeMinDate(picker)
        checkAfterMaxDate(picker)
    }

    private fun checkBeforeMinDate(picker: WheelPicker<*>) {
        picker.postDelayed({
            if (minDate != null && isBeforeMinDate(getDate())) {
                for (p in pickers) {
                    p.scrollTo(p.findIndexOfDate(minDate!!))
                }
            }
        }, DELAY_BEFORE_CHECK_PAST.toLong())
    }

    private fun checkAfterMaxDate(picker: WheelPicker<*>) {
        picker.postDelayed({
            if (maxDate != null && isAfterMaxDate(getDate())) {
                for (p in pickers) {
                    p.scrollTo(p.findIndexOfDate(maxDate!!))
                }
            }
        }, DELAY_BEFORE_CHECK_PAST.toLong())
    }

    private fun isBeforeMinDate(date: Date): Boolean {
        return DateHelper.getCalendarOfDate(date).before(DateHelper.getCalendarOfDate(minDate))
    }

    private fun isAfterMaxDate(date: Date): Boolean {
        return DateHelper.getCalendarOfDate(date).after(DateHelper.getCalendarOfDate(maxDate))
    }

    fun addOnDateChangedListener(listener: OnDateChangedListener) {
        this.listeners.add(listener)
    }

    fun removeOnDateChangedListener(listener: OnDateChangedListener) {
        this.listeners.remove(listener)
    }

    fun checkPickersMinMax() {
        for (picker in pickers) {
            checkMinMaxDate(picker)
        }
    }

    fun getDate(): Date {
        var hour = hoursPicker.currentHour
        if (isAmPm && amPmPicker.isPm) {
            hour += PM_HOUR_ADDITION
        }
        val minute = minutesPicker.currentMinute

        val calendar = Calendar.getInstance()

        if (displayDays) {
            val dayDate = daysPicker.currentDate
            calendar.time = dayDate
        } else {
            if (displayMonth) {
                calendar.set(Calendar.MONTH, monthPicker.currentMonth)
            }

            if (displayYears) {
                calendar.set(Calendar.YEAR, yearsPicker.currentYear)
            }

            if (displayDaysOfMonth) {
                val daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
                if (daysOfMonthPicker.currentDay >= daysInMonth) {
                    calendar.set(Calendar.DAY_OF_MONTH, daysInMonth)
                } else {
                    calendar.set(Calendar.DAY_OF_MONTH, daysOfMonthPicker.currentDay + 1)
                }
            }
        }

        calendar.set(Calendar.HOUR_OF_DAY, hour)
        calendar.set(Calendar.MINUTE, minute)

        return calendar.time
    }

    fun setStepMinutes(minutesStep: Int) {
        minutesPicker.setStepMinutes(minutesStep)
    }

    fun setHoursStep(hoursStep: Int) {
        hoursPicker.setHoursStep(hoursStep)
    }

    fun setDefaultDate(date: Date?) {
        if (date != null) {
            this.defaultDate = date

            updateDaysOfMonth()

            for (picker in pickers) {
                picker.setDefaultDate(defaultDate)
            }
        }
    }

    fun selectDate(calendar: Calendar?) {
        if (calendar == null) {
            return
        }

        val date = calendar.time
        for (picker in pickers) {
            picker.selectDate(date)
        }

        if (displayDaysOfMonth) {
            updateDaysOfMonth()
        }
    }

    private fun updateListener() {
        val date = getDate()
        val format = if (isAmPm) FORMAT_12_HOUR else FORMAT_24_HOUR
        val displayed = DateFormat.format(format, date).toString()
        for (listener in listeners) {
            listener.onDateChanged(displayed, date)
        }
    }

    private fun updateDaysOfMonth() {
        val date = getDate()
        val calendar = Calendar.getInstance()
        calendar.time = date
        updateDaysOfMonth(calendar)
    }

    private fun updateDaysOfMonth(calendar: Calendar) {
        val daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
        daysOfMonthPicker.daysInMonth = daysInMonth
        daysOfMonthPicker.updateAdapter()
    }

    fun setMustBeOnFuture(mustBeOnFuture: Boolean) {
        this.mustBeOnFuture = mustBeOnFuture
        if (mustBeOnFuture) {
            minDate = Calendar.getInstance().time // minDate is Today
        }
    }

    fun mustBeOnFuture(): Boolean {
        return mustBeOnFuture
    }

    private fun setMinYear() {

        if (displayYears && this.minDate != null && this.maxDate != null) {
            val calendar = Calendar.getInstance()
            calendar.time = this.minDate
            yearsPicker.setMinYear(calendar.get(Calendar.YEAR))
            calendar.time = this.maxDate
            yearsPicker.setMaxYear(calendar.get(Calendar.YEAR))
        }
    }

    private fun checkSettings() {
        if (displayDays && (displayDaysOfMonth || displayMonth)) {
            throw IllegalArgumentException("You can either display days with months or days and months separately")
        }
    }

    private fun init(context: Context, attrs: AttributeSet?) {
        attrs?.let {
            val a = context.obtainStyledAttributes(attrs, R.styleable.SingleDateAndTimePicker)

            val resources = resources
            setTodayText(a.getString(R.styleable.SingleDateAndTimePicker_picker_todayText))
            setTextColor(a.getColor(R.styleable.SingleDateAndTimePicker_picker_textColor, ContextCompat.getColor(context, R.color.picker_default_text_color)))
            setSelectedTextColor(a.getColor(R.styleable.SingleDateAndTimePicker_picker_selectedTextColor, ContextCompat.getColor(context, R.color.picker_default_selected_text_color)))
            setSelectorColor(a.getColor(R.styleable.SingleDateAndTimePicker_picker_selectorColor, ContextCompat.getColor(context, R.color.picker_default_selector_color)))
            setSelectorHeight(a.getDimensionPixelSize(R.styleable.SingleDateAndTimePicker_picker_selectorHeight, resources.getDimensionPixelSize(R.dimen.wheelSelectorHeight)))
            setTextSize(a.getDimensionPixelSize(R.styleable.SingleDateAndTimePicker_picker_textSize, resources.getDimensionPixelSize(R.dimen.WheelItemTextSize)))
            setCurved(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_curved, IS_CURVED_DEFAULT))
            setCyclic(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_cyclic, IS_CYCLIC_DEFAULT))
            setMustBeOnFuture(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_mustBeOnFuture, MUST_BE_ON_FUTUR_DEFAULT))
            setVisibleItemCount(a.getInt(R.styleable.SingleDateAndTimePicker_picker_visibleItemCount, VISIBLE_ITEM_COUNT_DEFAULT))

            setDisplayDays(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayDays, displayDays))
            setDisplayMinutes(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayMinutes, displayMinutes))
            setDisplayHours(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayHours, displayHours))
            setDisplayMonths(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayMonth, displayMonth))
            setDisplayYears(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayYears, displayYears))
            setDisplayDaysOfMonth(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayDaysOfMonth, displayDaysOfMonth))
            setDisplayMonthNumbers(a.getBoolean(R.styleable.SingleDateAndTimePicker_picker_displayMonthNumbers, monthPicker.displayMonthNumbers()))

            checkSettings()
            setMinYear()

            a.recycle()

            if (displayDaysOfMonth) {
                updateDaysOfMonth(Calendar.getInstance())
            }
        }
    }

    interface OnDateChangedListener {
        fun onDateChanged(displayed: String, date: Date)
    }
}

single_day_picker.xml

**<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:gravity="center"
    android:orientation="horizontal">

    <com.github.florent37.singledateandtimepicker.widget.WheelMonthPicker
        android:id="@+id/monthPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        app:wheel_atmospheric="true"
        app:wheel_item_align="right" />

    <com.github.florent37.singledateandtimepicker.widget.WheelDayOfMonthPicker
        android:id="@+id/daysOfMonthPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        app:wheel_atmospheric="true"
        app:wheel_item_align="right" />

    <com.github.florent37.singledateandtimepicker.widget.WheelDayPicker
        android:id="@+id/daysPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:wheel_atmospheric="true"
        app:wheel_item_align="right" />

    <com.github.florent37.singledateandtimepicker.widget.WheelYearPicker
        android:id="@+id/yearPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        app:wheel_atmospheric="true"
        app:wheel_item_align="right" />

    <com.github.florent37.singledateandtimepicker.widget.WheelHourPicker
        android:id="@+id/hoursPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        app:wheel_atmospheric="true"
        app:wheel_item_align="center" />

    <com.github.florent37.singledateandtimepicker.widget.WheelMinutePicker
        android:id="@+id/minutesPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:wheel_atmospheric="true"
        app:wheel_item_align="center" />

    <com.github.florent37.singledateandtimepicker.widget.WheelAmPmPicker
        android:id="@+id/amPmPicker"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:paddingLeft="30dp"
        app:wheel_atmospheric="true"
        app:wheel_item_align="center"
        app:wheel_visible_item_count="2" />

</LinearLayout>

<View
    android:id="@+id/dtSelector"
    android:layout_width="match_parent"
    android:layout_height="@dimen/wheelSelectorHeight"
    android:layout_gravity="center_vertical"
    android:alpha="0.2"
    android:background="@color/picker_default_selector_color" />

**

The result.

Screen Shot 2019-08-28 at 22 17 49
mofada commented 5 years ago

Okay thank you

chinalwb commented 4 years ago

@agustinsivoplas Your answer helps me a lot, thank you man!