由于以前公司的产品需求,须要作一个符合日历view作某些功能,当时想着说,日历view仍是挺简单的,官方也有封装,要不了多久,所以提早调研的时候主要看了一下官方的CalendarView以及一些github上的开源日历view。git
然而等到开始开发,拿到UI和UE的交互图时,我傻了,和说好的不同啊,UE跟我说,你去看看飞猪的那个日历,交互和那个差很少。行吧,只能从新撸代码了。github
飞猪的那个日历,是一个竖向日历,和显示连续几个月的日期,而后不可选择的日期标灰,而后还会显示一些节假日,支持区间选择,每月份有一个悬停的标题。这边咱们的需求没有区间选择,可是有每一个日期的view有不一样的显示需求。bash
一个竖向滚动的日历,最顶部是周几,每月的日期数不同,要有留白,以及顶部的月份标识,我以为你们看到这种需求,第一反应都是RecyclerView对吧,固然我也是这么想的,每日都是一个item,而后留白就是空的占位,大体的思路是能够决定了的。app
由于顶部的星期是固定显示的,并且是横向铺开的,因此一个LinearLayout便可实现ide
val week = LinearLayout(context).apply {
val headParams = LayoutParams(LayoutParams.MATCH_PARENT, dip2px(context, 30f))
layoutParams = headParams
orientation = HORIZONTAL
setBackgroundColor(ContextCompat.getColor(context,R.color.color_F1F5F8))
setPadding(dip2px(context, 15f), 0, dip2px(context, 15f), 0)
}
val list = listOf("日", "一", "二", "三", "四", "五", "六")
val itemParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)
for(i in list){
val tv = TextView(context).apply {
layoutParams = itemParams
gravity = Gravity.CENTER
setTextColor(ContextCompat.getColor(context,R.color.color_92a0aa))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
text = i
}
week.addView(tv)
}
复制代码
作的比较简陋,顶部星期的显示写成了一个固定的值,若是配置成xml属性,能够经过属性去控制每周的起始日是星期天仍是星期一函数
须要显示日期的话,咱们得把须要构造一个List,将全部须要显示的数据放进List,再将它放到RecyclerView的Adapter里。由于日期分月和天天,所以咱们须要两个dataBean,一个是月份的Bean,一个是天天日期的Bean。性能
每月的数据是一个MonthBean,里面包含年份,月份,以及一个List,List内包含的是这个月的天天的DateBean。每日的DateBean,须要的数据有,年费,月份,日期,同时由于置灰以及点击的判断,咱们还须要一些布尔值去判断。那么这样基本能够肯定MonthBean和DateBean的类型,大体代码以下:ui
MonthBeanspa
data class MonthBean(var year: Int, var month: Int, var dateList: MutableList<DateBean> = mutableListOf())
复制代码
DateBean.net
data class DateBean(var year: Int = 2019, var month: Int, var day: Int, var type: Int,
var isToday: Boolean = false, //是不是当天
var isChooseDay: Boolean = false, // 是不是选择日期
) {
// 分组
val groupName: String
get() {
val sMonth = if (month < 10) String.format("0%d", month) else String.format("%d", month)
return year.toString() + "年" + sMonth + "月"
}
// 当日的准确日期
val date: String
get() {
val sMonth = if (month < 10) String.format("0%d", month) else String.format("%d", month)
val sDate = if (day < 10) String.format("0%d", day) else String.format("%d", day)
return year.toString() + sMonth + sDate
}
}
复制代码
接下来咱们就能够构建Adapter所须要的list了
val calendar = Calendar.getInstance()
calendar.add(Calendar.MONTH, -MAX_MONTH_COUNT + 1)
for (i in 0 until MAX_MONTH_COUNT) {
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH) + 1
val bean = MonthInfoBean(year, month)
monthList.add(bean)
calendar.add(Calendar.MONTH, 1)
}
复制代码
MAX_MONTH_COUNT指的是最多显示的月份,而后把须要显示的月份的数据add到Calendar的实例内,monthList是须要展现的月份的MonthBean的List,以后再循环给monthList内添加dateList,同时经过Calendar的计算,去添加月份初始以及月份结束的空白的占位符,代码较长,具体的代码以后能够看附上的github连接。 经过上面的循环,咱们能够构造出一个含有空白占位符的每日的List,以后咱们再给它添加title,做为每月份的title。
完成了List的建立,咱们就能够建立对应的Adapter以及Decoration。Adapter内咱们须要区分View的种类,月份的title,空白日期的占位符,以及显示每日的数据。
Adapter内咱们能够定义三种ViewType,去绘制不一样的item。分割线和悬浮的title只须要重写RecyclerView.ItemDecoration内的onDrawOver函数,便可作到,下面是大体代码
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val manager = parent.layoutManager as GridLayoutManager
val position = manager.findFirstVisibleItemPosition()
if (position == RecyclerView.NO_POSITION) {
return
}
val viewHolder = parent.findViewHolderForAdapterPosition(position)
val item = viewHolder?.itemView
var flag = false
// 判断下一个title是否滑动上来了
if (isLast(position) && null != item) {
if (item.height + item.top < top) {
c.save()
flag = true
c.translate(0f, (item.height + item.top - top).toFloat())
}
}
// 绘制title
val rect = RectF(
0f,
parent.paddingTop.toFloat(),
parent.right.toFloat(),
(parent.paddingTop + top).toFloat())
c.drawRect(rect, mPaint)
c.drawText(mCallBack(position),
rect.centerX(),
rect.centerY() + mTopPadding,
mTextPaint)
if (flag) {
c.restore()
}
}
复制代码
mCallBack是根据position取对应item的分组的回调函数,isLast是判断当前的位置是否是处于最后一排。 以后将Adapter和Decoration加到对应的RecyclerView内就能够完成了。
大体效果以下gif
这种方式实现的日历,功能比较简单,也没有特别多的扩展功能,并且由于使用的是RecyclerView,若是须要显示的数据多了,代码性能是不太OK的,并且构造日历的部分的代码是循环套循环,时间复杂度上也是很是低效的。也就是抠脚写法,代码写的也挺简陋,具体细节能够参考github连接,欢迎各位拿去修改使用,提出各类意见啥的。
最后放上项目的github地址 连接
参考博客和项目