sequence

Because before the company’s product needs, need to do a CalendarView to do some functions, thinking at that time, CalendarView is quite simple, the official packaging, it will not take long, so in advance of the research time mainly looked at the official CalendarView and some github open source CalendarView.

However, when I got the interaction diagram between UI and UE at the beginning of development, I was stupid. It was different from what I had promised. UE told me to check the calendar of flying Pig, and the interaction was similar to that one. All right, we’re just gonna have to recode.

The calendar of flying pigs, is a vertical calendar, and shows consecutive months of dates, and then non-selectable dates gray, and then will also show some holidays, support interval selection, each month has a hovering title. We don’t have interval selection here, but views with each date have different display requirements.

Train of thought

A vertical scrolling calendar, the top is the day of the week, the number of dates in each month is different, there should be white space, and the month logo at the top. I think the first reaction of everyone when they see this demand is RecyclerView, right? Of course, I think so, every day is an item, and then white space is empty space. The general idea can be determined.

implementation

Top week display

Because the top of the week is fixed display, and is spread out horizontally, so a LinearLayout can be implemented

    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("Day"."一"."二"."Three"."Four"."Five"."Six")
    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)
    }
    
Copy the code

In a crude way, the top day of the week display is written as a fixed value, which, if configured as an XML attribute, controls whether the week starts on Sunday or Monday

The date shown

To display dates, we need to create a List, put all the data we need to display into the List, and then put it into the RecyclerView Adapter. Because dates are month and day, we need two dataBean beans, one for the month and one for the day.

So how do you build this List?

The data of each month is a MonthBean, which contains the year, month, and a List. The List contains the DateBean of each day of the month. For the daily DateBean, we need data such as annual fee, month, date, and some Boolean values for the purpose of grashing and clicking. Then we can basically determine the type of MonthBean and DateBean, the approximate code is as follows:

MonthBean

data class MonthBean(var year: Int, var month: Int, var dateList: MutableList<DateBean> = mutableListOf())
Copy the code

DateBean

data class DateBean(var year: Int = 2019, var month: Int, var day: Int, var type: Int,
                    var isToday: Boolean = falseVar isChooseDay: Boolean =false// Group val groupName: Stringget() {
            val sMonth = if (month < 10) String.format("0%d", month) else String.format("%d", month)
            return year.toString() + "Year" + sMonth + "Month"} // The exact date of the day val date: Stringget() {
            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
        }

}
Copy the code

Now we can build the list required by the Adapter

    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)
    }
Copy the code

“MAX_MONTH_COUNT” indicates the month with the most numbers to be displayed. Then add data of monthList to the Calendar instance. “monthList” is the List of monthbeans to be displayed. At the same time, the blank placeholders of the beginning and end of the month are added through Calendar calculation. The code is long, and you can see the attached Github link after the specific code. Through the loop above, we can construct a daily List with blank placeholders, and then we can add title to it as the title for each month.

Build corresponding Adapters and decorations

After creating the List, we can create the corresponding Adapter and Decoration. Within the Adapter we need to differentiate between View types, month titles, placeholders for blank dates, and display daily data.

There are three viewTypes that we can define in Adapter to draw different items. Line and suspended the title only needs to be rewritten RecyclerView. ItemDecoration onDrawOver function, can do, is roughly the code below

    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// Determine if the next title slides upif(isLast(position) && null ! = item) {if (item.height + item.top < top) {
                c.save()
                flag = trueC ranslate(0f, (item.height + item.top-top).tofloat ())}} 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()
        }
    }
Copy the code

MCallBack is a callback function that fetches the corresponding item according to position, and isLast is a callback function that determines whether the current position is in the last row. Then add Adapter and Decoration to the corresponding RecyclerView to complete.

The effect

The general effect is GIF

conclusion

This way to achieve the calendar, the function is relatively simple, there is no special extension function, and because of the use of RecyclerView, if the need to display more data, the code performance is not OK, and the construction of the calendar part of the code is a cycle cycle, time complexity is also very inefficient. The code is also quite simple. For details, please refer to github link. Welcome to modify and use it and put forward various opinions.

Finally, put a link to the project’s Github address

Consult blogs and projects

Blog.csdn.net/Demo_Jin/ar…

Github.com/huanghaibin…