android做一个类似钉钉的日历时间选择器

Categories: android

最近做一个日期选择器,仿照钉钉上的,它有三种模式:日期选择、日期间隔、日期+时间。日期是用一个月历选择的,时间是滚动的TimePicker。 大概的样子是这样的:

  1. 日期选择

    1558069562450588.2019-05-17 13_12_38

  2. 日期间隔

1558069567543421.2019-05-17 13_13_27

  1. 日期+时间 1558069562438557.2019-05-17 13_11_08

布局

整个界面主要是一个头部(显示当前选中的日期时间),一个日历或TimePicker,一个底部(操作按钮)。用ConstraintLayout把整个布局先调整好: Screen Shot 2019-05-17 at 11.13.17

外框用的是Android现在默认的ConstraintLayout来布局,这个约束布局功能很是强大,现在Android layout文件默认生成也是这个布局,强烈推荐,它的强大之处网上一搜一大把!

上图那个放在屏幕外面的方块是一个TimePicker,因为在日期+时间选择的时候才用到,并且会有个过渡动画,所以先把它放屏幕外面。

日历,因为我们项目中用了一个Github上的开源的MaterialCalendar 所以就直接拿来用了,然后把这个CalendarView的背景色设置成透明,下面放一个大的TextView,这个日历下面的月份就有了。应该给这个TextView设置一个特殊的字体就比较好看点。

功能

有了整体布局只要把这个布局用到一个DialogFragment中,用的时候传入对应类型参数显示DialogFragment就行了,日期选择器和日期间隔选择器,因为有了现成的CalendarView就变的很简单,选中日期,刷新头部的TextView,点击确定把值回调出去就行了。 这个CalendarView提供了选中事件、月份改变事件:

override fun onDateSelected(p0: MaterialCalendarView, p1: CalendarDay, p2: Boolean) {
        if (isStartDateCalendarMode) {
            tv_start_date.text = DateHelper.getDate(p1.date)
            //如果是日期时间选择类型就切换到时间选择器
            if (pickerType == DATE_TIME_PICKER_TYPE) {
                animationForTimePickerVisible()
                //如果是日期间隔类型就把calendarView设置成结束日期
            } else if (pickerType == DATEINTERVAL_PICKER_TYPE) {
                showEndDateCalendar()
            }
        }else {
            tv_end_date.text = DateHelper.getDate(p1.date)
        }
    }

    override fun onMonthChanged(p0: MaterialCalendarView?, p1: CalendarDay?) {
        if (p1!= null) {//月份变化就动态修改底部月份的TextView的值
            setCalendarBg(p1.date)
        }
    }

日期+时间选择器,就有个View的切换过程,在日历上选中一个日期后,马上就切换成时间选择器,还有个过渡动画。

然后这个时间选择器TimePicker,本来就是系统自带的View,但是sdk21之后变成了一个时钟的样子,不过它还是提供了一个属性timePickerMode 可以决定这个TimePicker的显示模式,是老的spinner模式就是滚动选择的模式,还是新的时钟模式。

动画

前面说了日历切换成TimePicker还有个过渡动画,我们用的是约束布局,这个约束布局有个关键帧动画使用起来很方便。 关键帧动画,就是要两个关键状态,一个开始状态,一个结束状态,中间的变化过程给系统自己完成。套用到这里就是把一个开始的布局,一个结束的布局提供给ConstraintSet,Android的TransitionManager就能用这个ConstraintSet执行动画了。 就比如开始的时候是layout1.xml,就是上面图片的布局。结束的时候是layout2.xml,布局如下: Screen Shot 2019-05-17 at 12.53.45

layout1.xml区别就是把calendarView移出到屏幕外面,把TimePicker放到屏幕中间了。

private val datePickerLayout: ConstraintSet by lazy { ConstraintSet() }
private val dateTimePickerLayout: ConstraintSet by lazy { ConstraintSet() }
...
//layout1的布局可以从当前的ConstraintLayout复制过来
datePickerLayout.clone(ConstraintLayoutView)
//没有加载的layout2 直接从布局文件复制过来
dateTimePickerLayout.clone(activity, R.layout.layout2)
...

//使用就很简单了,切换成TimePicker
TransitionManager.beginDelayedTransition(ConstraintLayoutView)
dateTimePickerLayout.applyTo(ConstraintLayoutView)
//切换回来
TransitionManager.beginDelayedTransition(ConstraintLayoutView)
datePickerLayout.applyTo(ConstraintLayoutView)

就这么简单,动画就完成了。

这个ConstraintSet就是把两个布局文件的所有View的约束条件都收集起来,然后应用到ConstraintLayout上面去,所以它有个前提,你这个布局文件中所有的View都要有id,它要做一一对应的。 用这样两个xml的方式比较简单明了,ConstraintSet它还提供了很多方法可以手动设置一些约束进去,然后应用到ConstraintLayout上去,但是不够直观设置起来比较麻烦。

最后:可能讲的不太清楚,想看源码的我放GitHub上了。