靓仔,靓女,你地用过MaterialDatePicker未啊

4,663 阅读5分钟

前言

在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 -> 合并多个DateValidator
    • CompositeDateValidator.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

还是那句话,首先我们先看看它的结构和提供的方法。如图。

除去继承DialogFragment所实现的方法,我们来看看它们分别做什么。

头两个静态方法:

  • todayInUtcMilliseconds() -> 返回当前时间的UTC时间戳
  • thisMonthInUtcMilliseconds() -> 返回当前月份的UTC时间戳

成员方法:

  • getHeaderText() -> 获得MaterialDatePicker显示时间的字符串
  • getSelection() -> 获得当前选中的Item
  • add\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>

四 完