Google's official Material Design components library for Android applications with comprehensive UI components, theming, and animations.
—
Date and time pickers for user input with Material Design styling and behavior.
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.DateValidatorPointBackward;
import com.google.android.material.datepicker.CompositeDateValidator;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
import android.util.Pair;Material Design date picker dialog for single dates or date ranges.
class MaterialDatePicker<S> extends DialogFragment {
// Factory methods for single date selection
static Builder<Long> todayInUtcMilliseconds();
static Builder<Long> datePicker();
// Factory method for date range selection
static Builder<Pair<Long, Long>> dateRangePicker();
// Selection access
S getSelection();
String getHeaderText();
// Positive button listeners
boolean addOnPositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener<S> listener);
boolean removeOnPositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener<S> listener);
void clearOnPositiveButtonClickListeners();
// Negative button listeners
boolean addOnNegativeButtonClickListener(View.OnClickListener listener);
boolean removeOnNegativeButtonClickListener(View.OnClickListener listener);
void clearOnNegativeButtonClickListeners();
// Cancel listeners
boolean addOnCancelListener(DialogInterface.OnCancelListener listener);
boolean removeOnCancelListener(DialogInterface.OnCancelListener listener);
void clearOnCancelListeners();
// Dismiss listeners
boolean addOnDismissListener(DialogInterface.OnDismissListener listener);
boolean removeOnDismissListener(DialogInterface.OnDismissListener listener);
void clearOnDismissListeners();
}
interface MaterialPickerOnPositiveButtonClickListener<S> {
void onPositiveButtonClick(S selection);
}
class MaterialDatePicker.Builder<S> {
Builder<S> setSelection(S selection);
Builder<S> setCalendarConstraints(CalendarConstraints calendarConstraints);
Builder<S> setTheme(@StyleRes int themeResId);
Builder<S> setTitleText(@StringRes int titleTextResId);
Builder<S> setTitleText(CharSequence charSequence);
Builder<S> setPositiveButtonText(@StringRes int positiveButtonTextResId);
Builder<S> setPositiveButtonText(CharSequence charSequence);
Builder<S> setNegativeButtonText(@StringRes int negativeButtonTextResId);
Builder<S> setNegativeButtonText(CharSequence charSequence);
Builder<S> setInputMode(int inputMode);
MaterialDatePicker<S> build();
}public static final int INPUT_MODE_CALENDAR = 0;
public static final int INPUT_MODE_TEXT = 1;// Single date picker
MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select date")
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
.build();
datePicker.addOnPositiveButtonClickListener(selection -> {
// Handle selected date (in UTC milliseconds)
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
String selectedDate = sdf.format(new Date(selection));
dateButton.setText(selectedDate);
});
datePicker.show(getSupportFragmentManager(), "date_picker");
// Date range picker
MaterialDatePicker<Pair<Long, Long>> dateRangePicker = MaterialDatePicker.Builder.dateRangePicker()
.setTitleText("Select dates")
.build();
dateRangePicker.addOnPositiveButtonClickListener(selection -> {
Long startDate = selection.first;
Long endDate = selection.second;
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd", Locale.getDefault());
String dateRange = sdf.format(new Date(startDate)) + " - " + sdf.format(new Date(endDate));
dateRangeButton.setText(dateRange);
});
dateRangePicker.show(getSupportFragmentManager(), "date_range_picker");
// Date picker with constraints
CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder();
constraintsBuilder.setStart(MaterialDatePicker.todayInUtcMilliseconds());
constraintsBuilder.setEnd(MaterialDatePicker.todayInUtcMilliseconds() + TimeUnit.DAYS.toMillis(365));
constraintsBuilder.setValidator(DateValidatorPointForward.now());
MaterialDatePicker<Long> constrainedPicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setTitleText("Select future date")
.build();Configuration for calendar navigation bounds and date validation.
class CalendarConstraints {
Month getStart();
Month getEnd();
Month getOpenAt();
DateValidator getDateValidator();
int getYearSpan();
int getMonthSpan();
}
class CalendarConstraints.Builder {
Builder setStart(long month);
Builder setEnd(long month);
Builder setOpenAt(long month);
Builder setValidator(DateValidator validator);
CalendarConstraints build();
}
interface CalendarConstraints.DateValidator {
boolean isValid(long date);
}// Forward from a specific date
class DateValidatorPointForward implements CalendarConstraints.DateValidator {
static DateValidatorPointForward from(long point);
static DateValidatorPointForward now();
boolean isValid(long date);
}
// Backward to a specific date
class DateValidatorPointBackward implements CalendarConstraints.DateValidator {
static DateValidatorPointBackward before(long point);
static DateValidatorPointBackward now();
boolean isValid(long date);
}
// Composite validator combining multiple validators
class CompositeDateValidator implements CalendarConstraints.DateValidator {
static DateValidator allOf(List<DateValidator> validators);
static DateValidator anyOf(List<DateValidator> validators);
boolean isValid(long date);
}// Date picker with validation - only future dates
CalendarConstraints constraints = new CalendarConstraints.Builder()
.setStart(MaterialDatePicker.todayInUtcMilliseconds())
.setValidator(DateValidatorPointForward.now())
.build();
MaterialDatePicker<Long> futureDatePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraints)
.setTitleText("Select appointment date")
.build();
// Date picker with range constraints - only current year
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MONTH, Calendar.JANUARY);
calendar.set(Calendar.DAY_OF_MONTH, 1);
long yearStart = calendar.getTimeInMillis();
calendar.set(Calendar.MONTH, Calendar.DECEMBER);
calendar.set(Calendar.DAY_OF_MONTH, 31);
long yearEnd = calendar.getTimeInMillis();
CalendarConstraints yearConstraints = new CalendarConstraints.Builder()
.setStart(yearStart)
.setEnd(yearEnd)
.setOpenAt(MaterialDatePicker.todayInUtcMilliseconds())
.build();
// Composite validator - weekdays only and future dates
DateValidator weekdaysOnly = new DateValidator() {
@Override
public boolean isValid(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
}
};
DateValidator compositeValidator = CompositeDateValidator.allOf(Arrays.asList(
DateValidatorPointForward.now(),
weekdaysOnly
));
CalendarConstraints businessDayConstraints = new CalendarConstraints.Builder()
.setValidator(compositeValidator)
.build();Material Design time picker dialog for selecting time values.
class MaterialTimePicker extends DialogFragment {
// Factory method
static Builder builder();
// Time access
int getHour();
int getMinute();
int getInputMode();
// Positive button listeners
boolean addOnPositiveButtonClickListener(View.OnClickListener listener);
boolean removeOnPositiveButtonClickListener(View.OnClickListener listener);
void clearOnPositiveButtonClickListeners();
// Negative button listeners
boolean addOnNegativeButtonClickListener(View.OnClickListener listener);
boolean removeOnNegativeButtonClickListener(View.OnClickListener listener);
void clearOnNegativeButtonClickListeners();
// Cancel listeners
boolean addOnCancelListener(DialogInterface.OnCancelListener listener);
boolean removeOnCancelListener(DialogInterface.OnCancelListener listener);
void clearOnCancelListeners();
// Dismiss listeners
boolean addOnDismissListener(DialogInterface.OnDismissListener listener);
boolean removeOnDismissListener(DialogInterface.OnDismissListener listener);
void clearOnDismissListeners();
}
class MaterialTimePicker.Builder {
Builder setTimeFormat(@TimeFormat int format);
Builder setHour(@IntRange(from = 0, to = 23) int hour);
Builder setMinute(@IntRange(from = 0, to = 59) int minute);
Builder setTitleText(@StringRes int titleTextResId);
Builder setTitleText(CharSequence charSequence);
Builder setPositiveButtonText(@StringRes int positiveButtonTextResId);
Builder setPositiveButtonText(CharSequence charSequence);
Builder setNegativeButtonText(@StringRes int negativeButtonTextResId);
Builder setNegativeButtonText(CharSequence charSequence);
Builder setInputMode(int inputMode);
Builder setTheme(@StyleRes int themeResId);
MaterialTimePicker build();
}public static final int INPUT_MODE_CLOCK = 0;
public static final int INPUT_MODE_KEYBOARD = 1;class TimeFormat {
public static final int CLOCK_12H = 0;
public static final int CLOCK_24H = 1;
}// Basic time picker
MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setHour(12)
.setMinute(0)
.setTitleText("Select appointment time")
.build();
timePicker.addOnPositiveButtonClickListener(v -> {
int hour = timePicker.getHour();
int minute = timePicker.getMinute();
// Format time for display
String timeString = String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
timeButton.setText(timeString);
// Convert to 12-hour format if needed
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
SimpleDateFormat sdf = new SimpleDateFormat("h:mm a", Locale.getDefault());
String formattedTime = sdf.format(calendar.getTime());
timeButton.setText(formattedTime);
});
timePicker.show(getSupportFragmentManager(), "time_picker");
// 24-hour format time picker
MaterialTimePicker timePicker24h = new MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H)
.setHour(14)
.setMinute(30)
.setTitleText("Meeting time")
.setInputMode(MaterialTimePicker.INPUT_MODE_KEYBOARD)
.build();
timePicker24h.addOnPositiveButtonClickListener(v -> {
int hour = timePicker24h.getHour();
int minute = timePicker24h.getMinute();
String time24h = String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
timeButton.setText(time24h);
});
// Time picker with validation
MaterialTimePicker businessHoursPicker = new MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setHour(9)
.setMinute(0)
.setTitleText("Business hours only")
.build();
businessHoursPicker.addOnPositiveButtonClickListener(v -> {
int hour = businessHoursPicker.getHour();
int minute = businessHoursPicker.getMinute();
// Validate business hours (9 AM - 5 PM)
if (hour < 9 || hour >= 17) {
showErrorDialog("Please select a time between 9:00 AM and 5:00 PM");
return;
}
// Save valid time
saveAppointmentTime(hour, minute);
});Example showing combined date and time selection with validation:
public class AppointmentSchedulerActivity extends AppCompatActivity {
private MaterialButton dateButton;
private MaterialButton timeButton;
private MaterialButton scheduleButton;
private Long selectedDateMillis;
private Integer selectedHour;
private Integer selectedMinute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appointment_scheduler);
initViews();
setupDatePicker();
setupTimePicker();
updateScheduleButton();
}
private void initViews() {
dateButton = findViewById(R.id.date_button);
timeButton = findViewById(R.id.time_button);
scheduleButton = findViewById(R.id.schedule_button);
scheduleButton.setOnClickListener(v -> scheduleAppointment());
}
private void setupDatePicker() {
dateButton.setOnClickListener(v -> {
// Create constraints for future dates only
CalendarConstraints constraints = new CalendarConstraints.Builder()
.setStart(MaterialDatePicker.todayInUtcMilliseconds())
.setValidator(DateValidatorPointForward.now())
.build();
MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select appointment date")
.setSelection(selectedDateMillis != null ? selectedDateMillis : MaterialDatePicker.todayInUtcMilliseconds())
.setCalendarConstraints(constraints)
.build();
datePicker.addOnPositiveButtonClickListener(selection -> {
selectedDateMillis = selection;
// Format date for display
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
String dateString = sdf.format(new Date(selection));
dateButton.setText(dateString);
updateScheduleButton();
});
datePicker.show(getSupportFragmentManager(), "date_picker");
});
}
private void setupTimePicker() {
timeButton.setOnClickListener(v -> {
MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setHour(selectedHour != null ? selectedHour : 9)
.setMinute(selectedMinute != null ? selectedMinute : 0)
.setTitleText("Select appointment time")
.build();
timePicker.addOnPositiveButtonClickListener(view -> {
int hour = timePicker.getHour();
int minute = timePicker.getMinute();
// Validate business hours
if (!isValidBusinessHour(hour)) {
showBusinessHoursError();
return;
}
selectedHour = hour;
selectedMinute = minute;
// Format time for display
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
SimpleDateFormat timeFormat = new SimpleDateFormat("h:mm a", Locale.getDefault());
String timeString = timeFormat.format(calendar.getTime());
timeButton.setText(timeString);
updateScheduleButton();
});
timePicker.show(getSupportFragmentManager(), "time_picker");
});
}
private boolean isValidBusinessHour(int hour) {
return hour >= 9 && hour < 17; // 9 AM to 5 PM
}
private void showBusinessHoursError() {
new MaterialAlertDialogBuilder(this)
.setTitle("Invalid Time")
.setMessage("Please select a time between 9:00 AM and 5:00 PM")
.setPositiveButton("OK", null)
.show();
}
private void updateScheduleButton() {
boolean canSchedule = selectedDateMillis != null && selectedHour != null && selectedMinute != null;
scheduleButton.setEnabled(canSchedule);
}
private void scheduleAppointment() {
if (selectedDateMillis == null || selectedHour == null || selectedMinute == null) {
return;
}
// Create appointment datetime
Calendar appointmentTime = Calendar.getInstance();
appointmentTime.setTimeInMillis(selectedDateMillis);
appointmentTime.set(Calendar.HOUR_OF_DAY, selectedHour);
appointmentTime.set(Calendar.MINUTE, selectedMinute);
appointmentTime.set(Calendar.SECOND, 0);
appointmentTime.set(Calendar.MILLISECOND, 0);
// Check if appointment is at least 1 hour in the future
Calendar now = Calendar.getInstance();
now.add(Calendar.HOUR_OF_DAY, 1);
if (appointmentTime.before(now)) {
new MaterialAlertDialogBuilder(this)
.setTitle("Invalid Appointment Time")
.setMessage("Appointments must be scheduled at least 1 hour in advance.")
.setPositiveButton("OK", null)
.show();
return;
}
// Show confirmation dialog
SimpleDateFormat fullFormat = new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' h:mm a", Locale.getDefault());
String appointmentString = fullFormat.format(appointmentTime.getTime());
new MaterialAlertDialogBuilder(this)
.setTitle("Confirm Appointment")
.setMessage("Schedule appointment for " + appointmentString + "?")
.setPositiveButton("Confirm", (dialog, which) -> {
// Save appointment
saveAppointment(appointmentTime);
showConfirmationSnackbar();
})
.setNegativeButton("Cancel", null)
.show();
}
private void saveAppointment(Calendar appointmentTime) {
// Save to database or send to server
// Implementation depends on your data storage solution
}
private void showConfirmationSnackbar() {
Snackbar.make(findViewById(android.R.id.content),
"Appointment scheduled successfully", Snackbar.LENGTH_LONG)
.setAction("View Details", v -> {
// Navigate to appointment details
})
.show();
}
}
// Date range picker for vacation booking
public class VacationBookingFragment extends Fragment {
private MaterialButton dateRangeButton;
private TextView selectedDatesText;
private void setupDateRangePicker() {
dateRangeButton.setOnClickListener(v -> {
// Create constraints - no weekends, future dates only
DateValidator weekdaysOnly = date -> {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
};
DateValidator futureOnly = DateValidatorPointForward.now();
DateValidator combinedValidator = CompositeDateValidator.allOf(
Arrays.asList(futureOnly, weekdaysOnly)
);
CalendarConstraints constraints = new CalendarConstraints.Builder()
.setValidator(combinedValidator)
.setStart(MaterialDatePicker.todayInUtcMilliseconds())
.build();
MaterialDatePicker<Pair<Long, Long>> dateRangePicker =
MaterialDatePicker.Builder.dateRangePicker()
.setTitleText("Select vacation dates")
.setCalendarConstraints(constraints)
.build();
dateRangePicker.addOnPositiveButtonClickListener(selection -> {
Long startDate = selection.first;
Long endDate = selection.second;
// Calculate number of days
long daysDiff = TimeUnit.MILLISECONDS.toDays(endDate - startDate) + 1;
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd", Locale.getDefault());
String dateRangeText = dateFormat.format(new Date(startDate)) +
" - " + dateFormat.format(new Date(endDate));
dateRangeButton.setText(dateRangeText);
selectedDatesText.setText(daysDiff + " days selected");
});
dateRangePicker.show(getParentFragmentManager(), "date_range_picker");
});
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-google-android-material--material