[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…

相關文章
相關標籤/搜索