9

I have a custom linear layout which has DatePicker and TimePicker widgets in it. This is used as DateTime picker. I want to restrict the DatePicker by way of setMaxDate() and setMinDate(). I have done:

dp.setMaxDate(new DateTime().minusDays(1).getMillis());
dp.setMinDate(new DateTime().minusDays(30).getMillis());

The dates beyond this range are actually grayed out when the dialog appears. But I can still select a grayed date. I have stumbled upon this. But, what is the work around for a custom layout with datepicker in it?

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
Vivekanand P V
  • 861
  • 3
  • 13
  • 27

3 Answers3

15

For future readers!

Actually with new android material design components what you want achieve could be achieved using MaterialDatePicker. And dates outside the allowed range is not selectable.

restricted datepicker range


Steps

1. Add material dependency to your module's gradle file

implementation 'com.google.android.material:material:1.1.0-beta01'

2. Change app theme to inherit from a version of material theme.

ex:

<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">

3. Use following code to initiate the dialog.

class MainActivity : AppCompatActivity() {

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

        setupRangePickerDialog()

    }

    private fun setupRangePickerDialog() {
        val builderRange = MaterialDatePicker.Builder.dateRangePicker()

        builderRange.setCalendarConstraints(limitRange().build())
        val pickerRange = builderRange.build()
        pickerRange.show(supportFragmentManager, pickerRange.toString())
    }


    /*
    Limit selectable range to Oct 17 - Nov 20 2019
     */
    private fun limitRange(): CalendarConstraints.Builder {

        val constraintsBuilderRange = CalendarConstraints.Builder()

        val calendarStart: Calendar = GregorianCalendar.getInstance()
        val calendarEnd: Calendar = GregorianCalendar.getInstance()

        val year = 2019

        calendarStart.set(year, 10, 17)
        calendarEnd.set(year, 11, 20)

        val minDate = calendarStart.timeInMillis
        val maxDate = calendarEnd.timeInMillis

        constraintsBuilderRange.setStart(minDate)
        constraintsBuilderRange.setEnd(maxDate)

        constraintsBuilderRange.setValidator(RangeValidator(minDate, maxDate))

        return constraintsBuilderRange
    }


}

class RangeValidator(private val minDate:Long, private val maxDate:Long) : CalendarConstraints.DateValidator{


    constructor(parcel: Parcel) : this(
        parcel.readLong(),
        parcel.readLong()
    )

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        TODO("not implemented")
    }

    override fun describeContents(): Int {
        TODO("not implemented")
    }

    override fun isValid(date: Long): Boolean {
        return !(minDate > date || maxDate < date)

    }

    companion object CREATOR : Parcelable.Creator<RangeValidator> {
        override fun createFromParcel(parcel: Parcel): RangeValidator {
            return RangeValidator(parcel)
        }

        override fun newArray(size: Int): Array<RangeValidator?> {
            return arrayOfNulls(size)
        }
    }

}
user158
  • 12,852
  • 7
  • 62
  • 94
13

The Material Components Library provides the MaterialDatePicker.
You can use a DateValidator to restrict the selections.
In particular you can use the built-in validators:

  • DateValidatorPointForward that enables dates from a given point forward
  • DateValidatorPointBackward that enables only dates before a given point.

Something like:

MaterialDatePicker.Builder<Pair<Long, Long>> builderRange = MaterialDatePicker.Builder.dateRangePicker();
CalendarConstraints.Builder constraintsBuilderRange = new CalendarConstraints.Builder();

//....define min and max for example with LocalDateTime and ZonedDateTime or Calendar

CalendarConstraints.DateValidator dateValidatorMin = DateValidatorPointForward.from(min.getTimeInMillis());
CalendarConstraints.DateValidator dateValidatorMax = DateValidatorPointBackward.before(max.getTimeInMillis());

ArrayList<CalendarConstraints.DateValidator> listValidators =
            new ArrayList<CalendarConstraints.DateValidator>();
listValidators.add(dateValidatorMin);
listValidators.add(dateValidatorMax);
CalendarConstraints.DateValidator validators = CompositeDateValidator.allOf(listValidators);
constraintsBuilderRange.setValidator(validators);

builderRange.setCalendarConstraints(constraintsBuilderRange.build());
MaterialDatePicker<Pair<Long, Long>> pickerRange = builderRange.build();
pickerRange.show(getSupportFragmentManager(), pickerRange.toString());

enter image description here

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • Now all you have to do is to migrate to the Material theme and watch your app explode. Even the Bridge themes won't help mitigate crashes. All parts should be manually checked – Farid Mar 05 '21 at 07:46
  • 1
    @Farid the answer is about the validator with the MaterialDatePicker not the theme needed to use. – Gabriele Mariotti Mar 05 '21 at 11:22
4

If somebody wants RangeValidator (posted above) but with less code: (@Parcelize annotation added)

@Parcelize
internal class RangeValidator(
private var minDate: Long = 0,
private var maxDate: Long = 0) : DateValidator {

     override fun isValid(date: Long): Boolean {
         return !(minDate > date || maxDate < date)
     }
}
Eugene Voronoy
  • 1,384
  • 1
  • 14
  • 18