日历控件定制是移动开发平台上比较常见的而且比较难的需求,一般会遇到以下问题:
- 性能差,加载速度慢,原因是各种基于GridView或RecyclerView等ViewGroup实现的日历,控件数太多,假设一个月视图界面有42个item,每个item里面分别就有2个子TextView:天数、农历数和本身3个控件,这样一个月视图就有42 * 3+1(RecyclerView or GridView),清楚ViewPager特性的开发者就会明白,一般ViewPager持有3个item,那么一个日历控件持有的View控件数的数量将达到 1(ViewPager)+ 3(RecyclerView or GridView) + 3 * 42 * 3 = 382,如果用1个View来代替RecyclerView等,用Canvas来代替各种TextView,那View的数量瞬间将下降360+,内存和性能优势将相当明显了
- 难定制 一般日历框架发布的同时也将UI风格确定下来了,假如人人都使用这个日历框架,那么将会千篇一律,难以突出自己的风格,要么就得改源码,成本太大,不太实际
- 功能性不足 例如无法自定义周起始、无法更改选择模式、动态设置UI等等
- 无法满足产品经理提出的变态需求 今天产品经历说我们要这样的实现、明天跟你说这里得改、后天说我们得限制一些日期...
但现在有了全新的 CalendarView 控件,它解锁了各种姿势,而且你可以不断调教它,直到你满足为止...
国际惯例:先放项目github地址
https://github.com/huanghaibin-dev/CalendarView
国内惯例:无图言吊
CalendarView的骚特性
- 基于Canvas绘制,极速性能
- 热插拔思想,任意定制周视图、月视图,即插即用!
- 支持单选、多选、国内手机日历默认自动选择等选择模式
- 支持静态、动态设置周起始,一行代码搞定
- 支持静态、动态设置日历项高度、日历填充模式
- 支持设置任意日期范围、任意拦截日期
- 支持多点触控、手指平滑切换过渡,拒绝界面抖动
- 既然这么多支持,那一定支持英语、繁体、简体,任意定制实现
接下来请看CalendarView骚操作,看看它是可以怎样调教的
- 你这样继承自己的月视图和周视图,只需要依次实现绘制选中:onDrawSelected、绘制事务:onDrawScheme、绘制文本:onDrawText这三个回调即可,参数和坐标都已经在回调函数上实现好,周视图也是一样的逻辑,只是不需要y参数
- /**
- * 定制高仿魅族日历界面,按你的想象力绘制出各种各样的界面
- * Created by huanghaibin on 2017/11/15.
- */
-
- public class MeiZuMonthView extends MonthView {
-
- /**
- * 绘制选中的日子
- *
- * @param canvas canvas
- * @param calendar 日历日历calendar
- * @param x 日历Card x起点坐标
- * @param y 日历Card y起点坐标
- * @param hasScheme hasScheme 非标记的日期
- * @return 返回true 则绘制onDrawScheme,因为这里背景色不是是互斥的,所以返回true
- */
- @Override
- protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme) {
- canvas.drawRect(x + mPadding, y + mPadding, x + mItemWidth - mPadding, y + mItemHeight - mPadding, mSelectedPaint);
- return true;
- }
-
- /**
- * 绘制标记的事件日子
- *
- * @param canvas canvas
- * @param calendar 日历calendar
- * @param x 日历Card x起点坐标
- * @param y 日历Card y起点坐标
- */
- @Override
- protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y) {
- canvas.drawCircle(x + mItemWidth - mPadding - mRadio / 2, y + mPadding + mRadio, mRadio, mSchemeBasicPaint);
- canvas.drawText(calendar.getScheme(),
- x + mItemWidth - mPadding - mRadio / 2 - getTextWidth(calendar.getScheme()) / 2,
- y + mPadding + mSchemeBaseLine, mTextPaint);
- }
-
- /**
- * 绘制文本
- *
- * @param canvas canvas
- * @param calendar 日历calendar
- * @param x 日历Card x起点坐标
- * @param y 日历Card y起点坐标
- * @param hasScheme 是否是标记的日期
- * @param isSelected 是否选中
- */
- @Override
- protected void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected) {
- int cx = x + mItemWidth / 2;
- int top = y - mItemHeight / 6;
-
- boolean isInRange = isInRange(calendar);
-
- if (isSelected) {
- canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
- mSelectTextPaint);
- canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, mSelectedLunarTextPaint);
- } else if (hasScheme) {
- canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
- calendar.isCurrentMonth() && isInRange ? mSchemeTextPaint : mOtherMonthTextPaint);
-
- canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, mCurMonthLunarTextPaint);
- } else {
- canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
- calendar.isCurrentDay() ? mCurDayTextPaint :
- calendar.isCurrentMonth() && isInRange ? mCurMonthTextPaint : mOtherMonthTextPaint);
- canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10,
- calendar.isCurrentDay() && isInRange ? mCurDayLunarTextPaint :
- calendar.isCurrentMonth() ? mCurMonthLunarTextPaint : mOtherMonthLunarTextPaint);
- }
- }
- }
- 复制代码
- 当你实现好之后,直接在xml界面上添加特性,可以即时预览效果:
-
- <attr name="month_view" format="string" /><!--自定义月视图路径-->
- <attr name="week_view" format="string" /> <!--自定义周视图路径-->
-
- app:month_view="com.haibin.calendarviewproject.MeiZuCalendarCardView"
- app:week_view="com.haibin.calendarviewproject.MeiZuWeekView"
-
- 复制代码
- 但这种静态模式可能无法满足你的需求,你可能需要动态变换定制的视图界面,于是你可以使用热插拔特性,即插即用,不爽就换:
-
- mCalendarView.setWeekView(MeizuWeekView.class);
-
- mCalendarView.setMonthView(MeizuMonthView.class);
-
- 复制代码
- CalendarView也提供了高效便利的年视图,可以快速切换年份、月份,十分便利
- 但年视图也不一定就适合产品经理的胃口,产品经理希望像小米日历一样,弹出DatePickerView,通过它来跳转日期,于是你可以使用以下的API来让日历与其它控件联动
-
- CalendarView.scrollToCalendar();
-
- CalendarView.scrollToNext();
-
- CalendarView.scrollToPre();
-
- CalendarView.scrollToXXX();
-
- 复制代码
- 你也许需要像魅族日历一样,可以静态、动态更换周起始
-
- app:week_start_with="mon、sun、sat"
-
- CalendarView.setWeekStarWithSun();
-
- CalendarView.setWeekStarWithMon();
-
- CalendarView.setWeekStarWithSat();
-
- 复制代码
- 假如你是做酒店、旅游等应用场景的APP的,那么需要可选范围的日历,你可以这样继承,和普通视图实现完全一样
- public class CustomRangeMonthView extends RangeMonthView{
-
- }
-
- public class CustomRangeWeekView extends RangeWeekView{
-
- }
-
- 复制代码
-
然后你需要设置选择模式为范围模式:select_mode="range_mode"
-
酒店式日历场景当然是不能从昨天开始订房的,也不能无限期订房,所以你需要静态或动态设置日历范围、精确到具体某一天!!!
- <attr name="min_year" format="integer" />
- <attr name="max_year" format="integer" />
- <attr name="min_year_month" format="integer" />
- <attr name="max_year_month" format="integer" />
- <attr name="min_year_day" format="integer" />
- <attr name="max_year_day" format="integer" />
-
- CalendarView.setRange(int minYear, int minYearMonth, int minYearDay,
- int maxYear, int maxYearMonth, int maxYearDay)
-
- 复制代码
- 当然还有更特殊的日子也是不能选择的,例如:某月某号起这N天时间内因为超强台风来袭,酒店需停止营业N天,这段期间不可订房,这时日期拦截器就排上用场了
- //设置日期拦截事件
- mCalendarView.setOnCalendarInterceptListener(new CalendarView.OnCalendarInterceptListener() {
- @Override
- public boolean onCalendarIntercept(Calendar calendar) {
- //这里写拦截条件,返回true代表拦截
- return calendar.isWeekend();
- }
-
- @Override
- public void onCalendarInterceptClick(Calendar calendar, boolean isClick) {
- //todo 点击拦截的日期回调
- }
- });
- 复制代码
- 添加日期拦截器和范围设置后,你可以在周月视图按需求获得他们的结果
-
- boolean isInRange = isInRange(calendar);//日期是否在范围内,超出范围的可以置灰
-
- boolean isEnable = !onCalendarIntercept(calendar);//日期是否可用,没有被拦截,被拦截的可以置灰
-
- 复制代码
- 假如你是做清单类、任务类APP的,可能会有这样的需求:标记某天事务的进度,这也很简单,因为:日历界面长什么样,你自己说了算!!!
-
也许你只需要像原生日历那样就够了,但原生日历那奇怪且十分不友好的style,受到theme的影响,各种头疼,使用此控件,你只需要简简单单定制月视图就够了,CalendarView 能非常简单就高仿各种日历UI
-
CalendarView 提供了 setSchemeDate(Map<String, Calendar> mSchemeDates) 这个十分高效的API用来动态标记事务,即时你的数据量达到数千、数万、数十万,都不会对UI渲染造成影响
-
日历类 Calendar 提供了许多十分有用的API
- boolean isWeekend();//判断是不是周末,可以用不同的画笔绘制周末的样式
-
- int getWeek();//获取星期
-
- String getSolarTerm();//获取24节气,可以用不同颜色标记不同节日
-
- String getGregorianFestival();//获取公历节日,自由判断,把节日换上喜欢的颜色
-
- String getTraditionFestival();//获取传统节日
-
- boolean isLeapYear();//是否是闰年
-
- int getLeapMonth();//获取闰月
-
- boolean isSameMonth(Calendar calendar);//是否相同月
-
- int compareTo(Calendar calendar);//毕竟日期大小 -1 0 1
-
- long getTimeInMillis();//获取时间戳
-
- int differ(Calendar calendar);//日期运算,相差多少天
- 复制代码
其它各种场景姿势就不多说了,你得自己去解锁,一起看Demo以及各种APP的风骚实现