滿足你各種姿勢的最美Android開源日曆
- 效能差,載入速度慢 ,原因是各種基於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地址
ofollow,noindex"> 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 canvascanvas * @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 canvascanvas * @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 canvascanvas * @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也提供了高效便利的年檢視,可以快速切換年份、月份,十分便利

CalendarView.scrollToCalendar(); CalendarView.scrollToNext(); CalendarView.scrollToPre(); CalendarView.scrollToXXX(); 複製程式碼
- 你也許需要像魅族日曆一樣,可以靜態、動態更換周起始
app:week_start_with="mon、sun、sat" CalendarView.setWeekStarWithSun(); CalendarView.setWeekStarWithMon(); CalendarView.setWeekStarWithSat(); 複製程式碼
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的風騷實現
寫在最後,框架本身是為了解決各種各樣的場景而設計的,UI本身是靠自己繪製的,非常簡單,不懂的請優先看Demo,你可以自由發揮想象力定製最喜歡的日曆,只有你想不到,Demo基本給出了各種場景的實現思路。覺得可以的請給個star或者留下你寶貴的意見。