[Android]类飞猪的竖向的自定义日历View

由于以前公司的产品需求,须要作一个符合日历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。性能

那么如何构建这个List呢?

每月的数据是一个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。

构建对应的Adapter和Decoration

完成了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地址 连接

参考博客和项目

blog.csdn.net/Demo_Jin/ar…

github.com/huanghaibin…

相关文章
相关标签/搜索