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

因为之前公司的产品需求,需要做一个符合日历view做某些功能,当时想着说,日历view还是挺简单的,官方也有封装,要不了多久,因此提前调研的时候主要看了一下官方的CalendarView以及一些github上的开源日历view。

然而等到开始开发,拿到UI和UE的交互图时,我傻了,和说好的不一样啊,UE跟我说,你去看看飞猪的那个日历,交互和那个差不多。行吧,只能重新撸代码了。

飞猪的那个日历,是一个竖向日历,和显示连续几个月的日期,然后不可选择的日期标灰,然后还会显示一些节假日,支持区间选择,每个月份有一个悬停的标题。这边我们的需求没有区间选择,但是有每个日期的view有不同的显示需求。

思路

一个竖向滚动的日历,最顶部是周几,每个月的日期数不一样,要有留白,以及顶部的月份标识,我觉得大家看到这种需求,第一反应都是RecyclerView对吧,当然我也是这么想的,每日都是一个item,然后留白就是空的占位,大致的思路是可以决定了的。

实现

顶部星期显示

因为顶部的星期是固定显示的,而且是横向铺开的,所以一个LinearLayout即可实现

    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的类型,大致代码如下:

MonthBean

data class MonthBean(var year: Int, var month: Int, var dateList: MutableList<DateBean> = mutableListOf())
复制代码

DateBean

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…

转载于:https://juejin.im/post/5d26dc6b6fb9a07eff00b25f

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值