前言
在Android应用的开发中,我们经常会使用到日期选择控件,根据官方教程的写法,我们会得到下图的一个日期选择控件(图左)。(这绿色还挺好看)。其实在Google新的material库中,包含了一个新的日期选择控件,就是MaterialDatePicker(图右)。

默认界面对比
- 主题色。左边是
colorAccent,右边是colorPrimary - Title。 左边不支持自定义一个标题,右边可以。(左上角的MATERIALDATEPICKER)
- 切换模式按钮。右边多了一个铅笔的按钮,点击它可以切换手动输入模式。
- .........
新功能
- 支持区域选择日期
- 支持显示Title(额,和上面重复了,不管了)
- 支持切换模式(日历模式和输入模式)
- 支持限制选择日期的范围。
Hello MaterialDatePicker
〇 必不可少的准备功夫
第一步,先导入Material依赖库。在build.gradle中插入
implementation "com.google.android.material:material:1.2.0-alpha06"
第二步,切换为Material Theme
Theme.AppCompat.Light.DarkActionBar==>Theme.MaterialComponents.Light.DarkActionBar
一 构建MaterialDatePicker
MaterialDatePicker.Builder
首先我们先看看MaterialDatePicker.Builder里,有什么方法供我们使用。

可以看到头三个方法都是静态方法,它们的返回值都是Builder<S>,它们的目的就是返回一个Builder,那它们的区别在哪呢?
customDatePicker(DateSeletor)-> 返回实现自定义DateSeletor的Builder。datePicker()-> 返回单选日期的Builder。dateRangePicker()-> 返回区域选择日期的Builder。
那么这时候问题就来了,<S>s是什么,DataSelector又是什么。我们来看看文档。
Interface for users of link
MaterialCalendar<S>to control how the Calendar displays and returns selections.Dates are represented as times in UTC milliseconds.
<S>The type of item available when cells are selected in the AdapterView
DateSeletor 是一个控制日历控件显示和返回选择项。
S是传入的选择项类型。
PS:谷歌已经写好了单选和区域选择日期的DataSeletor供我们使用。
成员方法中除去最后一个build()方法,还有5种set方法供我们调用。
setSelection(S)-> 设置默认选择项setTheme(int)-> 设置主题setCalendarConstraints(CalendarConstraint)-> 设置可选日期限制范围setTitleText(int/CharSequence)-> 设置显示在左上角文本的内容setInputMode(int)-> 设置日期选择器的模式,MaterialDatePicker.INPUT_MODE_TEXT-> 键盘输入模式MaterialDatePicker.INPUT_MODE_CALENDAR-> 日历选择模式
上面出现了一个新类,CalendarConstraint顾名思义就是约束日历的范围,那我们怎么通过CalendarConstraint来限制日期的可选范围呢?
CalendarConstraint.Builder
同样的,我们看看在CalendarConstraint.Builder里,有什么方法供我们使用的。

如图所示,都是成员方法。
long 参数均为UTC时间戳
setStart(long)-> 限制开始的月份setEnd(long)-> 限制结束的月份setOpenAt(long)-> 设置打开时默认显示的月份setValidator(DateValidator)-> 设置日期限定器,限定哪些日子是有效的可选择的。
DateValidator
同样的,先看看代码和注释。
Used to determine whether calendar days are enabled.
Returns true if the provided date is enabled.
public interface DateValidator extends Parcelable { boolean isValid(long date); }
DateValidator 是一个接口,通过isValid(long)方法判断传入的date能否被选择进而达到限制效果。
谷歌已经实现了三个DateValidator供我们使用。分别是:
CompositeDateValidator-> 合并多个DateValidatorCompositeDateValidator.allOf(List<DateValidator>)-> 返回合并多个DateValidator的限定器
DateValidatorPointBackward-> 限制后面的日期都不能选择DateValidatorPointBackward.before(long)-> 限制传入的date后面的日期都不能选择的限定器DateValidatorPointBackward.now()-> 限制现在时间后面的日期都不能选择的限定器
DateValidatorPointForward-> 限制前面的日期都不能选择DateValidatorPointForward.from(long)-> 返回限制传入的date前面的日期都不能选择的限定器DateValidatorPointForward.now()-> 返回限制现在时间前面的日期都不能选择的限定器。
emmmmmm,理论的知识终于说完了。现在上码。
构建单选日期MaterialDatePicker
public void showDatePicker(String title) {
//当前月份(4月)
long thisMonth = MaterialDatePicker.thisMonthInUtcMilliseconds();
//上2个月(2月)
long preMonth = DateUtil.changeMonth(thisMonth, -2);
//下一个月(5月)
long nextMonth = DateUtil.changeMonth(thisMonth, 1);
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
//可选择的月历范围
//从2月开始
.setStart(preMonth)
//到5月结束
.setEnd(nextMonth)
//一打开默认是2月
.setOpenAt(preMonth)
//可选择的日期范围
//2020-4-1 ~ 今天
.setValidator(CompositeDateValidator.allOf(Arrays.asList(
DateValidatorPointForward.from(DateUtil.getTimeStampByDate(2020, 4, 1)),
DateValidatorPointBackward.now()
)))
.build();
//返回一个单选日期的MaterialDatePicker
MaterialDatePicker.Builder<Long> builder = MaterialDatePicker.Builder.datePicker();
//设置默认选中时间(今天)
builder.setSelection(MaterialDatePicker.todayInUtcMilliseconds());
//设置要显示出来的Title
builder.setTitleText(title);
//设置日期限定器
builder.setCalendarConstraints(calendarConstraints);
//设置日历模式
builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR);
//build一个MaterialDatePicker
MaterialDatePicker<Long> picker = builder.build();
//显示
picker.show(getSupportFragmentManager(), picker.toString());
构建区域选择日期的MaterialDatePicker
public void showRangePicker(String title) {
//当前月份(4月)
long thisMonth = MaterialDatePicker.thisMonthInUtcMilliseconds();
//上3个月(1月)
long preMonth = DateUtil.changeMonth(thisMonth, -3);
//下2个月(6月)
long nextMonth = DateUtil.changeMonth(thisMonth, 2);
//2月2
long feb2 = DateUtil.getTimeStampByDate(2020, 2, 2);
//5月4
long may4 = DateUtil.getTimeStampByDate(2020, 5, 4);
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
//可选择的月历范围
//从1月开始
.setStart(preMonth)
//到6月结束
.setEnd(nextMonth)
//默认显示1月
.setOpenAt(preMonth)
//可选择的日期范围
//2020-2-2 ~ 今天
.setValidator(CompositeDateValidator.allOf(Arrays.asList(
DateValidatorPointForward.from(feb2),
DateValidatorPointBackward.now()
)))
.build();
MaterialDatePicker.Builder<Pair<Long, Long>> builder = MaterialDatePicker.Builder.dateRangePicker();
//设置选中时间 2月2~5月4
builder.setSelection(new Pair<>(feb2, may4));
//设置要显示出来的Title
builder.setTitleText(title);
//设置日期限定器
builder.setCalendarConstraints(calendarConstraints);
//设置日历模式
builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR);
//build一个MaterialDatePicker
MaterialDatePicker<Pair<Long, Long>> picker = builder.build();
//显示
picker.show(getSupportFragmentManager(), picker.toString());
}
二 处理MaterialDatePicker
还是那句话,首先我们先看看它的结构和提供的方法。如图。

头两个静态方法:
todayInUtcMilliseconds()-> 返回当前时间的UTC时间戳thisMonthInUtcMilliseconds()-> 返回当前月份的UTC时间戳
成员方法:
getHeaderText()-> 获得MaterialDatePicker显示时间的字符串getSelection()-> 获得当前选中的Itemadd\remove\clear-> 添加、移除、清除监听器PositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener)-> 确认按钮监听器(参数就是选中项)NegativeButtonClickListener(OnClickListener)-> 取消按钮监听器CancelListener(CancelListener)-> Dialog取消监听器(点了Dialog外部或返回键)DismissListener(OnDismissListener)-> Dialog消失监听器
处理MaterialDatePicker
//build一个MaterialDatePicker
MaterialDatePicker<Long> picker = builder.build();
//显示
picker.show(getSupportFragmentManager(), picker.toString());
picker.addOnPositiveButtonClickListener(selection -> {
String time = DateUtil.timeStampToDate(selection, DateUtil.PATTERN_YYYY_MM_DD);
binding.tvTime.setText(time);
});
picker.addOnNegativeButtonClickListener(view -> {
long i = Objects.requireNonNull(picker.getSelection());
String t = picker.getHeaderText();
binding.tvTime.setText(t);
Toast.makeText(this, "点了取消按钮" + i, Toast.LENGTH_SHORT).show();
});
picker.addOnCancelListener(dialogInterface ->
Toast.makeText(this, "Dialog取消监听器", Toast.LENGTH_SHORT).show());
picker.addOnDismissListener(dialogInterface -> {
Toast.makeText(this, "Dialog消失监听器", Toast.LENGTH_SHORT).show();
});
三 主题
直接上码,按需修改。
<style name="ThemeOverlay.MaterialComponents.MaterialCalendar" parent="ThemeOverlay.MaterialComponents.Dialog">
<item name="materialCalendarStyle">@style/Widget.MaterialComponents.MaterialCalendar</item>
<!-- Header Styles -->
<item name="materialCalendarHeaderLayout">@style/Widget.MaterialComponents.MaterialCalendar.HeaderLayout</item>
<item name="materialCalendarHeaderDivider">@style/Widget.MaterialComponents.MaterialCalendar.HeaderDivider</item>
<item name="materialCalendarHeaderTitle">@style/Widget.MaterialComponents.MaterialCalendar.HeaderTitle</item>
<item name="materialCalendarHeaderSelection">@style/Widget.MaterialComponents.MaterialCalendar.HeaderSelection</item>
<item name="materialCalendarHeaderConfirmButton">@style/Widget.MaterialComponents.MaterialCalendar.HeaderConfirmButton</item>
<item name="materialCalendarHeaderToggleButton">@style/Widget.MaterialComponents.MaterialCalendar.HeaderToggleButton</item>
<!-- Grid Styles -->
<item name="materialCalendarDay">@style/Widget.MaterialComponents.MaterialCalendar.DayTextView</item>
<!-- Action Styles -->
<item name="buttonBarPositiveButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog</item>
<item name="buttonBarNegativeButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog</item>
</style>