日曆控制元件定製是移動開發平臺上比較常見的而且比較難的需求,一般會遇到以下問題:
- 效能差,載入速度慢,原因是各種基於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的風騷實現