0
# Pickers and Selection
1
2
Date and time pickers for user input with Material Design styling and behavior.
3
4
## Core Imports
5
6
```java
7
import com.google.android.material.datepicker.MaterialDatePicker;
8
import com.google.android.material.datepicker.CalendarConstraints;
9
import com.google.android.material.datepicker.DateValidatorPointForward;
10
import com.google.android.material.datepicker.DateValidatorPointBackward;
11
import com.google.android.material.datepicker.CompositeDateValidator;
12
import com.google.android.material.timepicker.MaterialTimePicker;
13
import com.google.android.material.timepicker.TimeFormat;
14
import android.util.Pair;
15
```
16
17
## Material Date Picker
18
19
Material Design date picker dialog for single dates or date ranges.
20
21
```java { .api }
22
class MaterialDatePicker<S> extends DialogFragment {
23
// Factory methods for single date selection
24
static Builder<Long> todayInUtcMilliseconds();
25
static Builder<Long> datePicker();
26
27
// Factory method for date range selection
28
static Builder<Pair<Long, Long>> dateRangePicker();
29
30
// Selection access
31
S getSelection();
32
String getHeaderText();
33
34
// Positive button listeners
35
boolean addOnPositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener<S> listener);
36
boolean removeOnPositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener<S> listener);
37
void clearOnPositiveButtonClickListeners();
38
39
// Negative button listeners
40
boolean addOnNegativeButtonClickListener(View.OnClickListener listener);
41
boolean removeOnNegativeButtonClickListener(View.OnClickListener listener);
42
void clearOnNegativeButtonClickListeners();
43
44
// Cancel listeners
45
boolean addOnCancelListener(DialogInterface.OnCancelListener listener);
46
boolean removeOnCancelListener(DialogInterface.OnCancelListener listener);
47
void clearOnCancelListeners();
48
49
// Dismiss listeners
50
boolean addOnDismissListener(DialogInterface.OnDismissListener listener);
51
boolean removeOnDismissListener(DialogInterface.OnDismissListener listener);
52
void clearOnDismissListeners();
53
}
54
55
interface MaterialPickerOnPositiveButtonClickListener<S> {
56
void onPositiveButtonClick(S selection);
57
}
58
59
class MaterialDatePicker.Builder<S> {
60
Builder<S> setSelection(S selection);
61
Builder<S> setCalendarConstraints(CalendarConstraints calendarConstraints);
62
Builder<S> setTheme(@StyleRes int themeResId);
63
Builder<S> setTitleText(@StringRes int titleTextResId);
64
Builder<S> setTitleText(CharSequence charSequence);
65
Builder<S> setPositiveButtonText(@StringRes int positiveButtonTextResId);
66
Builder<S> setPositiveButtonText(CharSequence charSequence);
67
Builder<S> setNegativeButtonText(@StringRes int negativeButtonTextResId);
68
Builder<S> setNegativeButtonText(CharSequence charSequence);
69
Builder<S> setInputMode(int inputMode);
70
MaterialDatePicker<S> build();
71
}
72
```
73
74
### Date Picker Input Mode Constants
75
76
```java { .api }
77
public static final int INPUT_MODE_CALENDAR = 0;
78
public static final int INPUT_MODE_TEXT = 1;
79
```
80
81
### Usage Example
82
83
```java
84
// Single date picker
85
MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
86
.setTitleText("Select date")
87
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
88
.build();
89
90
datePicker.addOnPositiveButtonClickListener(selection -> {
91
// Handle selected date (in UTC milliseconds)
92
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
93
String selectedDate = sdf.format(new Date(selection));
94
dateButton.setText(selectedDate);
95
});
96
97
datePicker.show(getSupportFragmentManager(), "date_picker");
98
99
// Date range picker
100
MaterialDatePicker<Pair<Long, Long>> dateRangePicker = MaterialDatePicker.Builder.dateRangePicker()
101
.setTitleText("Select dates")
102
.build();
103
104
dateRangePicker.addOnPositiveButtonClickListener(selection -> {
105
Long startDate = selection.first;
106
Long endDate = selection.second;
107
108
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd", Locale.getDefault());
109
String dateRange = sdf.format(new Date(startDate)) + " - " + sdf.format(new Date(endDate));
110
dateRangeButton.setText(dateRange);
111
});
112
113
dateRangePicker.show(getSupportFragmentManager(), "date_range_picker");
114
115
// Date picker with constraints
116
CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder();
117
constraintsBuilder.setStart(MaterialDatePicker.todayInUtcMilliseconds());
118
constraintsBuilder.setEnd(MaterialDatePicker.todayInUtcMilliseconds() + TimeUnit.DAYS.toMillis(365));
119
constraintsBuilder.setValidator(DateValidatorPointForward.now());
120
121
MaterialDatePicker<Long> constrainedPicker = MaterialDatePicker.Builder.datePicker()
122
.setCalendarConstraints(constraintsBuilder.build())
123
.setTitleText("Select future date")
124
.build();
125
```
126
127
## Calendar Constraints
128
129
Configuration for calendar navigation bounds and date validation.
130
131
```java { .api }
132
class CalendarConstraints {
133
Month getStart();
134
Month getEnd();
135
Month getOpenAt();
136
DateValidator getDateValidator();
137
int getYearSpan();
138
int getMonthSpan();
139
}
140
141
class CalendarConstraints.Builder {
142
Builder setStart(long month);
143
Builder setEnd(long month);
144
Builder setOpenAt(long month);
145
Builder setValidator(DateValidator validator);
146
CalendarConstraints build();
147
}
148
149
interface CalendarConstraints.DateValidator {
150
boolean isValid(long date);
151
}
152
```
153
154
### Date Validators
155
156
```java { .api }
157
// Forward from a specific date
158
class DateValidatorPointForward implements CalendarConstraints.DateValidator {
159
static DateValidatorPointForward from(long point);
160
static DateValidatorPointForward now();
161
boolean isValid(long date);
162
}
163
164
// Backward to a specific date
165
class DateValidatorPointBackward implements CalendarConstraints.DateValidator {
166
static DateValidatorPointBackward before(long point);
167
static DateValidatorPointBackward now();
168
boolean isValid(long date);
169
}
170
171
// Composite validator combining multiple validators
172
class CompositeDateValidator implements CalendarConstraints.DateValidator {
173
static DateValidator allOf(List<DateValidator> validators);
174
static DateValidator anyOf(List<DateValidator> validators);
175
boolean isValid(long date);
176
}
177
```
178
179
### Usage Example
180
181
```java
182
// Date picker with validation - only future dates
183
CalendarConstraints constraints = new CalendarConstraints.Builder()
184
.setStart(MaterialDatePicker.todayInUtcMilliseconds())
185
.setValidator(DateValidatorPointForward.now())
186
.build();
187
188
MaterialDatePicker<Long> futureDatePicker = MaterialDatePicker.Builder.datePicker()
189
.setCalendarConstraints(constraints)
190
.setTitleText("Select appointment date")
191
.build();
192
193
// Date picker with range constraints - only current year
194
Calendar calendar = Calendar.getInstance();
195
calendar.set(Calendar.MONTH, Calendar.JANUARY);
196
calendar.set(Calendar.DAY_OF_MONTH, 1);
197
long yearStart = calendar.getTimeInMillis();
198
199
calendar.set(Calendar.MONTH, Calendar.DECEMBER);
200
calendar.set(Calendar.DAY_OF_MONTH, 31);
201
long yearEnd = calendar.getTimeInMillis();
202
203
CalendarConstraints yearConstraints = new CalendarConstraints.Builder()
204
.setStart(yearStart)
205
.setEnd(yearEnd)
206
.setOpenAt(MaterialDatePicker.todayInUtcMilliseconds())
207
.build();
208
209
// Composite validator - weekdays only and future dates
210
DateValidator weekdaysOnly = new DateValidator() {
211
@Override
212
public boolean isValid(long date) {
213
Calendar cal = Calendar.getInstance();
214
cal.setTimeInMillis(date);
215
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
216
return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
217
}
218
};
219
220
DateValidator compositeValidator = CompositeDateValidator.allOf(Arrays.asList(
221
DateValidatorPointForward.now(),
222
weekdaysOnly
223
));
224
225
CalendarConstraints businessDayConstraints = new CalendarConstraints.Builder()
226
.setValidator(compositeValidator)
227
.build();
228
```
229
230
## Material Time Picker
231
232
Material Design time picker dialog for selecting time values.
233
234
```java { .api }
235
class MaterialTimePicker extends DialogFragment {
236
// Factory method
237
static Builder builder();
238
239
// Time access
240
int getHour();
241
int getMinute();
242
int getInputMode();
243
244
// Positive button listeners
245
boolean addOnPositiveButtonClickListener(View.OnClickListener listener);
246
boolean removeOnPositiveButtonClickListener(View.OnClickListener listener);
247
void clearOnPositiveButtonClickListeners();
248
249
// Negative button listeners
250
boolean addOnNegativeButtonClickListener(View.OnClickListener listener);
251
boolean removeOnNegativeButtonClickListener(View.OnClickListener listener);
252
void clearOnNegativeButtonClickListeners();
253
254
// Cancel listeners
255
boolean addOnCancelListener(DialogInterface.OnCancelListener listener);
256
boolean removeOnCancelListener(DialogInterface.OnCancelListener listener);
257
void clearOnCancelListeners();
258
259
// Dismiss listeners
260
boolean addOnDismissListener(DialogInterface.OnDismissListener listener);
261
boolean removeOnDismissListener(DialogInterface.OnDismissListener listener);
262
void clearOnDismissListeners();
263
}
264
265
class MaterialTimePicker.Builder {
266
Builder setTimeFormat(@TimeFormat int format);
267
Builder setHour(@IntRange(from = 0, to = 23) int hour);
268
Builder setMinute(@IntRange(from = 0, to = 59) int minute);
269
Builder setTitleText(@StringRes int titleTextResId);
270
Builder setTitleText(CharSequence charSequence);
271
Builder setPositiveButtonText(@StringRes int positiveButtonTextResId);
272
Builder setPositiveButtonText(CharSequence charSequence);
273
Builder setNegativeButtonText(@StringRes int negativeButtonTextResId);
274
Builder setNegativeButtonText(CharSequence charSequence);
275
Builder setInputMode(int inputMode);
276
Builder setTheme(@StyleRes int themeResId);
277
MaterialTimePicker build();
278
}
279
```
280
281
### Time Picker Input Mode Constants
282
283
```java { .api }
284
public static final int INPUT_MODE_CLOCK = 0;
285
public static final int INPUT_MODE_KEYBOARD = 1;
286
```
287
288
### Time Format Constants
289
290
```java { .api }
291
class TimeFormat {
292
public static final int CLOCK_12H = 0;
293
public static final int CLOCK_24H = 1;
294
}
295
```
296
297
### Usage Example
298
299
```java
300
// Basic time picker
301
MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
302
.setTimeFormat(TimeFormat.CLOCK_12H)
303
.setHour(12)
304
.setMinute(0)
305
.setTitleText("Select appointment time")
306
.build();
307
308
timePicker.addOnPositiveButtonClickListener(v -> {
309
int hour = timePicker.getHour();
310
int minute = timePicker.getMinute();
311
312
// Format time for display
313
String timeString = String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
314
timeButton.setText(timeString);
315
316
// Convert to 12-hour format if needed
317
Calendar calendar = Calendar.getInstance();
318
calendar.set(Calendar.HOUR_OF_DAY, hour);
319
calendar.set(Calendar.MINUTE, minute);
320
321
SimpleDateFormat sdf = new SimpleDateFormat("h:mm a", Locale.getDefault());
322
String formattedTime = sdf.format(calendar.getTime());
323
timeButton.setText(formattedTime);
324
});
325
326
timePicker.show(getSupportFragmentManager(), "time_picker");
327
328
// 24-hour format time picker
329
MaterialTimePicker timePicker24h = new MaterialTimePicker.Builder()
330
.setTimeFormat(TimeFormat.CLOCK_24H)
331
.setHour(14)
332
.setMinute(30)
333
.setTitleText("Meeting time")
334
.setInputMode(MaterialTimePicker.INPUT_MODE_KEYBOARD)
335
.build();
336
337
timePicker24h.addOnPositiveButtonClickListener(v -> {
338
int hour = timePicker24h.getHour();
339
int minute = timePicker24h.getMinute();
340
341
String time24h = String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
342
timeButton.setText(time24h);
343
});
344
345
// Time picker with validation
346
MaterialTimePicker businessHoursPicker = new MaterialTimePicker.Builder()
347
.setTimeFormat(TimeFormat.CLOCK_12H)
348
.setHour(9)
349
.setMinute(0)
350
.setTitleText("Business hours only")
351
.build();
352
353
businessHoursPicker.addOnPositiveButtonClickListener(v -> {
354
int hour = businessHoursPicker.getHour();
355
int minute = businessHoursPicker.getMinute();
356
357
// Validate business hours (9 AM - 5 PM)
358
if (hour < 9 || hour >= 17) {
359
showErrorDialog("Please select a time between 9:00 AM and 5:00 PM");
360
return;
361
}
362
363
// Save valid time
364
saveAppointmentTime(hour, minute);
365
});
366
```
367
368
## Complete Date/Time Selection Example
369
370
Example showing combined date and time selection with validation:
371
372
```java
373
public class AppointmentSchedulerActivity extends AppCompatActivity {
374
private MaterialButton dateButton;
375
private MaterialButton timeButton;
376
private MaterialButton scheduleButton;
377
378
private Long selectedDateMillis;
379
private Integer selectedHour;
380
private Integer selectedMinute;
381
382
@Override
383
protected void onCreate(Bundle savedInstanceState) {
384
super.onCreate(savedInstanceState);
385
setContentView(R.layout.activity_appointment_scheduler);
386
387
initViews();
388
setupDatePicker();
389
setupTimePicker();
390
updateScheduleButton();
391
}
392
393
private void initViews() {
394
dateButton = findViewById(R.id.date_button);
395
timeButton = findViewById(R.id.time_button);
396
scheduleButton = findViewById(R.id.schedule_button);
397
398
scheduleButton.setOnClickListener(v -> scheduleAppointment());
399
}
400
401
private void setupDatePicker() {
402
dateButton.setOnClickListener(v -> {
403
// Create constraints for future dates only
404
CalendarConstraints constraints = new CalendarConstraints.Builder()
405
.setStart(MaterialDatePicker.todayInUtcMilliseconds())
406
.setValidator(DateValidatorPointForward.now())
407
.build();
408
409
MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
410
.setTitleText("Select appointment date")
411
.setSelection(selectedDateMillis != null ? selectedDateMillis : MaterialDatePicker.todayInUtcMilliseconds())
412
.setCalendarConstraints(constraints)
413
.build();
414
415
datePicker.addOnPositiveButtonClickListener(selection -> {
416
selectedDateMillis = selection;
417
418
// Format date for display
419
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
420
String dateString = sdf.format(new Date(selection));
421
dateButton.setText(dateString);
422
423
updateScheduleButton();
424
});
425
426
datePicker.show(getSupportFragmentManager(), "date_picker");
427
});
428
}
429
430
private void setupTimePicker() {
431
timeButton.setOnClickListener(v -> {
432
MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
433
.setTimeFormat(TimeFormat.CLOCK_12H)
434
.setHour(selectedHour != null ? selectedHour : 9)
435
.setMinute(selectedMinute != null ? selectedMinute : 0)
436
.setTitleText("Select appointment time")
437
.build();
438
439
timePicker.addOnPositiveButtonClickListener(view -> {
440
int hour = timePicker.getHour();
441
int minute = timePicker.getMinute();
442
443
// Validate business hours
444
if (!isValidBusinessHour(hour)) {
445
showBusinessHoursError();
446
return;
447
}
448
449
selectedHour = hour;
450
selectedMinute = minute;
451
452
// Format time for display
453
Calendar calendar = Calendar.getInstance();
454
calendar.set(Calendar.HOUR_OF_DAY, hour);
455
calendar.set(Calendar.MINUTE, minute);
456
457
SimpleDateFormat timeFormat = new SimpleDateFormat("h:mm a", Locale.getDefault());
458
String timeString = timeFormat.format(calendar.getTime());
459
timeButton.setText(timeString);
460
461
updateScheduleButton();
462
});
463
464
timePicker.show(getSupportFragmentManager(), "time_picker");
465
});
466
}
467
468
private boolean isValidBusinessHour(int hour) {
469
return hour >= 9 && hour < 17; // 9 AM to 5 PM
470
}
471
472
private void showBusinessHoursError() {
473
new MaterialAlertDialogBuilder(this)
474
.setTitle("Invalid Time")
475
.setMessage("Please select a time between 9:00 AM and 5:00 PM")
476
.setPositiveButton("OK", null)
477
.show();
478
}
479
480
private void updateScheduleButton() {
481
boolean canSchedule = selectedDateMillis != null && selectedHour != null && selectedMinute != null;
482
scheduleButton.setEnabled(canSchedule);
483
}
484
485
private void scheduleAppointment() {
486
if (selectedDateMillis == null || selectedHour == null || selectedMinute == null) {
487
return;
488
}
489
490
// Create appointment datetime
491
Calendar appointmentTime = Calendar.getInstance();
492
appointmentTime.setTimeInMillis(selectedDateMillis);
493
appointmentTime.set(Calendar.HOUR_OF_DAY, selectedHour);
494
appointmentTime.set(Calendar.MINUTE, selectedMinute);
495
appointmentTime.set(Calendar.SECOND, 0);
496
appointmentTime.set(Calendar.MILLISECOND, 0);
497
498
// Check if appointment is at least 1 hour in the future
499
Calendar now = Calendar.getInstance();
500
now.add(Calendar.HOUR_OF_DAY, 1);
501
502
if (appointmentTime.before(now)) {
503
new MaterialAlertDialogBuilder(this)
504
.setTitle("Invalid Appointment Time")
505
.setMessage("Appointments must be scheduled at least 1 hour in advance.")
506
.setPositiveButton("OK", null)
507
.show();
508
return;
509
}
510
511
// Show confirmation dialog
512
SimpleDateFormat fullFormat = new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' h:mm a", Locale.getDefault());
513
String appointmentString = fullFormat.format(appointmentTime.getTime());
514
515
new MaterialAlertDialogBuilder(this)
516
.setTitle("Confirm Appointment")
517
.setMessage("Schedule appointment for " + appointmentString + "?")
518
.setPositiveButton("Confirm", (dialog, which) -> {
519
// Save appointment
520
saveAppointment(appointmentTime);
521
showConfirmationSnackbar();
522
})
523
.setNegativeButton("Cancel", null)
524
.show();
525
}
526
527
private void saveAppointment(Calendar appointmentTime) {
528
// Save to database or send to server
529
// Implementation depends on your data storage solution
530
}
531
532
private void showConfirmationSnackbar() {
533
Snackbar.make(findViewById(android.R.id.content),
534
"Appointment scheduled successfully", Snackbar.LENGTH_LONG)
535
.setAction("View Details", v -> {
536
// Navigate to appointment details
537
})
538
.show();
539
}
540
}
541
542
// Date range picker for vacation booking
543
public class VacationBookingFragment extends Fragment {
544
private MaterialButton dateRangeButton;
545
private TextView selectedDatesText;
546
547
private void setupDateRangePicker() {
548
dateRangeButton.setOnClickListener(v -> {
549
// Create constraints - no weekends, future dates only
550
DateValidator weekdaysOnly = date -> {
551
Calendar cal = Calendar.getInstance();
552
cal.setTimeInMillis(date);
553
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
554
return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
555
};
556
557
DateValidator futureOnly = DateValidatorPointForward.now();
558
DateValidator combinedValidator = CompositeDateValidator.allOf(
559
Arrays.asList(futureOnly, weekdaysOnly)
560
);
561
562
CalendarConstraints constraints = new CalendarConstraints.Builder()
563
.setValidator(combinedValidator)
564
.setStart(MaterialDatePicker.todayInUtcMilliseconds())
565
.build();
566
567
MaterialDatePicker<Pair<Long, Long>> dateRangePicker =
568
MaterialDatePicker.Builder.dateRangePicker()
569
.setTitleText("Select vacation dates")
570
.setCalendarConstraints(constraints)
571
.build();
572
573
dateRangePicker.addOnPositiveButtonClickListener(selection -> {
574
Long startDate = selection.first;
575
Long endDate = selection.second;
576
577
// Calculate number of days
578
long daysDiff = TimeUnit.MILLISECONDS.toDays(endDate - startDate) + 1;
579
580
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd", Locale.getDefault());
581
String dateRangeText = dateFormat.format(new Date(startDate)) +
582
" - " + dateFormat.format(new Date(endDate));
583
584
dateRangeButton.setText(dateRangeText);
585
selectedDatesText.setText(daysDiff + " days selected");
586
});
587
588
dateRangePicker.show(getParentFragmentManager(), "date_range_picker");
589
});
590
}
591
}
592
```