項目github地址
https://github.com/huanghaibin-dev/CalendarView
此框架采用組合的方式,各個模塊互相獨立,可自由采用各種提供的控件組合,完全自定義自己需要的UI,周視圖和月視圖可通過簡單自定義任意自由繪制,不怕美工提需求!!!下面教程將介紹如何實現(xiàn)3個API,自定義Canvas繪制日歷
CalendarView的優(yōu)勢:
1、熱插拔設(shè)計,根據(jù)不同的UI需求完全自定義UI,簡單幾步即可實現(xiàn),自定義事件日歷標(biāo)記、顏色、農(nóng)歷等
2、完全Canvas繪制,性能和速度都很不錯,相比大多數(shù)基于GridView或RecyclerView實現(xiàn)的占用內(nèi)存更低,啟動速度更快
3、支持收縮、展開、快速年月份選擇等
4、不要再問可不可以不顯示其它月份的日期,可以!!!,你可以任意配置到你喜歡為止;所以也不要問其它什么周末可不可以顯示灰色,其它什么可不可以,只要你稍微懂Canvas的API,這個控件真的可以!!!,UI是萬能繪制的
5、簡潔易懂的源碼,易學(xué)習(xí)。
Gradle
compile 'com.haibin:calendarview:3.2.0'
<dependency>
<groupId>com.haibin</groupId>
<artifactId>calendarview</artifactId>
<version>3.2.0</version>
<type>pom</type>
</dependency>
混淆proguard-rules
-keepclasseswithmembers class * {
public <init>(android.content.Context);
}
國際慣例,先上一個自定義效果圖,結(jié)尾還有幾個其它效果圖,可自己自定義
meizu.png
meizu_shrink.png
各個類功能介紹
CalendarLayout
這是個輔助類,負(fù)責(zé)CalendarView的收縮控制功能,如果不需要收縮功能,無需使用它,一般使用教程如下
<com.haibin.calendarview.CalendarLayout
android:id="@+id/calendarLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#fff"
app:only_week_view="false"
app:default_status="shrink"
app:calendar_content_view_id="@+id/recyclerView">
<com.haibin.calendarview.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
app:current_month_text_color="#333333"
app:current_month_lunar_text_color="#CFCFCF"
app:min_year="2004"
app:max_year="2020"
app:other_month_text_color="#e1e1e1"
app:other_month_lunar_text_color="#e1e1e1"
app:scheme_text="假"
app:scheme_text_color="#333"
app:scheme_theme_color="#128c4b"
app:selected_lunar_text_color="#CFCFCF"
app:month_view="com.haibin.calendarviewproject.meizu.MeiZuMonthView"
app:week_view="com.haibin.calendarviewproject.meizu.MeizuWeekView"
app:selected_text_color="#333"
app:selected_theme_color="#108cd4"
app:week_background="#fff"
app:week_text_color="#111"
app:year_view_day_text_color="#333333"
app:year_view_day_text_size="9sp"
app:year_view_month_text_color="#ff0000"
app:year_view_month_text_size="20sp"
app:month_view_show_mode="mode_only_current"
app:year_view_scheme_color="#f17706"/>
<com.haibin.calendarviewproject.group.GroupRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content_background" />
</com.haibin.calendarview.CalendarLayout>
CalendarLayout api
public void expand(); //展開
public void shrink(); //收縮
public boolean isExpand();//是否已經(jīng)展開
<declare-styleable name="CalendarLayout">
<attr name="default_status"> <!--默認(rèn)狀態(tài)-->
<enum name="expand" value="0" /><!--展開-->
<enum name="shrink" value="1" /><!--收縮-->
</attr>
<attr name="only_week_view" format="boolean" /><!--僅使用周視圖-->
<attr name="calendar_content_view_id" format="integer" /><!--如果需要放內(nèi)容,請標(biāo)記子View id-->
</declare-styleable>
CalendarView
真正的日歷類,可以自行通過attr配置
<com.haibin.calendarview.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
app:current_month_text_color="#333333"
app:current_month_lunar_text_color="#CFCFCF"
app:min_year="2004"
app:max_year="2020"
app:other_month_text_color="#e1e1e1"
app:other_month_lunar_text_color="#e1e1e1"
app:scheme_text="假"
app:scheme_text_color="#333"
app:scheme_theme_color="#128c4b"
app:selected_lunar_text_color="#CFCFCF"
app:month_view="com.haibin.calendarviewproject.meizu.MeiZuMonthView"
app:week_view="com.haibin.calendarviewproject.meizu.MeizuWeekView"
app:selected_text_color="#333"
app:selected_theme_color="#108cd4"
app:week_background="#fff"
app:week_text_color="#111"
app:year_view_day_text_color="#333333"
app:year_view_day_text_size="9sp"
app:year_view_month_text_color="#ff0000"
app:year_view_month_text_size="20sp"
app:year_view_scheme_color="#f17706"/>
完整attr
<declare-styleable name="CalendarView">
<attr name="week_background" format="color" /><!--星期欄背景色-->
<attr name="week_line_background" format="color" /><!--星期欄線的顏色-->
<attr name="week_text_color" format="color" /><!--星期欄文本顏色-->
<attr name="week_bar_view" format="string" /><!--如果需要的話使用自定義星期欄-->
<attr name="month_view" format="string" /><!--完全自定義月視圖路徑-->
<attr name="week_view" format="string" /><!--完全自定義周視圖路徑-->
<attr name="scheme_text" format="string" /><!--標(biāo)記的文本,一般可忽略-->
<attr name="day_text_size" format="dimension" /><!--日期字體大小-->
<attr name="lunar_text_size" format="dimension" /><!--農(nóng)歷字體大小-->
<attr name="calendar_height" format="dimension" /><!--日歷卡的高度,每一項,不是整體-->
<attr name="scheme_text_color" format="color" /><!--標(biāo)記的文本顏色-->
<attr name="scheme_month_text_color" format="color" /><!--標(biāo)記的月字體顏色-->
<attr name="scheme_lunar_text_color" format="color" /><!--標(biāo)記的農(nóng)歷字體顏色-->
<attr name="scheme_theme_color" format="color" /><!--標(biāo)記的背景主題顏色-->
<attr name="selected_theme_color" format="color" /><!--選擇的背景主題顏色-->
<attr name="selected_text_color" format="color" /><!--選中的月字體顏色-->
<attr name="selected_lunar_text_color" format="color" /><!--選中的農(nóng)歷字體顏色-->
<attr name="current_day_text_color" format="color" /><!--今天的日子字體顏色-->
<attr name="current_day_lunar_text_color" format="color" /><!--今天的日子農(nóng)歷字體顏色-->
<attr name="current_month_text_color" format="color" /><!--當(dāng)前月份卡的月字體顏色,非今天所在的月份-->
<attr name="other_month_text_color" format="color" /><!--當(dāng)前月份卡其它月字體顏色-->
<attr name="current_month_lunar_text_color" format="color" /><!--當(dāng)前月份月農(nóng)歷顏色-->
<attr name="other_month_lunar_text_color" format="color" /><!--其它月份農(nóng)歷字體顏色-->
<!-- 年視圖相關(guān) -->
<attr name="year_view_month_text_size" format="dimension" /><!--年視圖月字體大小-->
<attr name="year_view_day_text_size" format="dimension" /><!--年視圖日字體大小-->
<attr name="year_view_month_text_color" format="color" /><!--年視圖月字體顏色-->
<attr name="year_view_day_text_color" format="color" /><!--年視圖日子字體顏色-->
<attr name="year_view_scheme_color" format="color" /><!--年視圖標(biāo)記字體顏色-->
<attr name="year_view_background" format="color" /><!--年視圖背景-->
<!---->
<attr name="min_year" format="integer" /><!--最小年份-->
<attr name="max_year" format="integer" /><!--最大年份-->
<attr name="min_year_month" format="integer" /><!--最小年份對應(yīng)最小月份-->
<attr name="max_year_month" format="integer" /><!--最大年份對應(yīng)月份-->
<!--配置你喜歡的月視圖顯示模式模式-->
<attr name="month_view_show_mode">
<enum name="mode_all" value="0" /> <!--全部顯示-->
<enum name="mode_only_current" value="1" /> <!--僅顯示當(dāng)前月份-->
<enum name="mode_fix" value="2" /> <!--自適應(yīng)顯示,不會多出一行,但是會自動填充-->
</attr>
</declare-styleable>
CalendarView api
public int getCurDay(); //今天
public int getCurMonth(); //當(dāng)前的月份
public int getCurYear(); //今年
public void showSelectLayout(final int year); //快速彈出年份選擇月份
public void closeSelectLayout(final int position); //關(guān)閉選擇年份并跳轉(zhuǎn)日期
/**
* 設(shè)置日期范圍
*
* @param minYear 最小年份
* @param minYearMonth 最小年份對應(yīng)月份
* @param maxYear 最大月份
* @param maxYearMonth 最大月份對應(yīng)月份
*/
public void setRange(int minYear, int minYearMonth,
int maxYear, int maxYearMonth)
public void setOnYearChangeListener(OnYearChangeListener listener);//年份切換事件
public void setOnDateSelectedListener(OnDateSelectedListener listener);//日期選擇事件
public void setSchemeDate(List<Calendar> mSchemeDate);//標(biāo)記日期
public void update();//動態(tài)更新
public Calendar getSelectedCalendar(); //獲取選擇的日期
public void scrollToPre();//滾動到上一個月
public void scrollToNext();//滾動到下一個月
public void scrollToCalendar(int year, int month, int day);//滾動到指定日期
/**
* 設(shè)置背景色
*
* @param monthLayoutBackground 月份卡片的背景色
* @param weekBackground 星期欄背景色
* @param lineBg 線的顏色
*/
public void setBackground(int monthLayoutBackground, int weekBackground, int lineBg)
/**
* 設(shè)置文本顏色
*
* @param curMonthTextColor 當(dāng)前月份字體顏色
* @param otherMonthColor 其它月份字體顏色
* @param lunarTextColor 農(nóng)歷字體顏色
*/
public void setTextColor(int curMonthTextColor,int otherMonthColor,int lunarTextColor)
/**
* 設(shè)置選擇的效果
*
* @param style 選中的style CalendarCardView.STYLE_FILL or CalendarCardView.STYLE_STROKE
* @param selectedThemeColor 選中的標(biāo)記顏色
* @param selectedTextColor 選中的字體顏色
*/
public void setSelectedColor(int style, int selectedThemeColor, int selectedTextColor)
/**
* 設(shè)置標(biāo)記的色
*
* @param style 標(biāo)記的style CalendarCardView.STYLE_FILL or CalendarCardView.STYLE_STROKE
* @param schemeColor 標(biāo)記背景色
* @param schemeTextColor 標(biāo)記字體顏色
*/
public void setSchemeColor(int style, int schemeColor, int schemeTextColor)
/**
* 設(shè)置背景色
*
* @param yearViewBackground 年份卡片的背景色
* @param weekBackground 星期欄背景色
* @param lineBg 線的顏色
*/
public void setBackground(int yearViewBackground, int weekBackground, int lineBg)
這個控件的特別之處就是它的UI是可以交給客戶端自由繪制的,因此可以自由發(fā)揮想象力,繪制你需要的日歷效果UI
接下來介紹如何完全自定義日歷,自定義日歷需要同時自定義月視圖和周視圖,代碼幾乎一樣,需要實現(xiàn)三個回調(diào)函數(shù)即可,如下:
onDrawSelected
onDrawScheme
onDrawText
/**
* 首先繼承月視圖,假如我們想實現(xiàn)高仿魅族的日歷
*/
public class MeiZuMonthView extends MonthView {
/**
* 自定義魅族標(biāo)記的文本畫筆
*/
private Paint mTextPaint = new Paint();
/**
* 自定義魅族標(biāo)記的圓形背景
*/
private Paint mSchemeBasicPaint = new Paint();
private float mRadio;
private int mPadding;
private float mSchemeBaseLine;
public MeiZuMonthView(Context context) {
super(context);
mTextPaint.setTextSize(dipToPx(context, 8));
mTextPaint.setColor(0xffffffff);
mTextPaint.setAntiAlias(true);
mTextPaint.setFakeBoldText(true);
mSchemeBasicPaint.setAntiAlias(true);
mSchemeBasicPaint.setStyle(Paint.Style.FILL);
mSchemeBasicPaint.setTextAlign(Paint.Align.CENTER);
mSchemeBasicPaint.setFakeBoldText(true);
mRadio = dipToPx(getContext(), 7);
mPadding = dipToPx(getContext(), 4);
Paint.FontMetrics metrics = mSchemeBasicPaint.getFontMetrics();
mSchemeBaseLine = mRadio - metrics.descent + (metrics.bottom - metrics.top) / 2 + dipToPx(getContext(), 1);
}
/**
* 繪制選中的日子
* @param canvas canvas
* @param calendar 日歷日歷calendar
* @param x 日歷Card x起點坐標(biāo)
* @param y 日歷Card y起點坐標(biāo)
* @param hasScheme hasScheme 非標(biāo)記的日期
* @return 返回true 則會繼續(xù)繪制onDrawScheme,因為這里背景色不是是互斥的,所以返回true,返回false,則點擊scheme標(biāo)記的日子,則不繼續(xù)繪制onDrawScheme,自行選擇即可
*/
@Override
protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme) {
mSelectedPaint.setStyle(Paint.Style.FILL);
mSelectedPaint.setColor(0x80cfcfcf);
canvas.drawRect(x + mPadding, y + mPadding, x + mItemWidth - mPadding, y + mItemHeight - mPadding, mSelectedPaint);
return true;
}
/**
* 繪制標(biāo)記的事件日子
* @param canvas canvas
* @param calendar 日歷calendar
* @param x 日歷Card x起點坐標(biāo)
* @param y 日歷Card y起點坐標(biāo)
*/
@Override
protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y) {
mSchemeBasicPaint.setColor(calendar.getSchemeColor());
canvas.drawCircle(x + mItemWidth - mPadding - mRadio / 2, y + mPadding + mRadio, mRadio, mSchemeBasicPaint);
canvas.drawText(calendar.getScheme(), x + mItemWidth - mPadding - mRadio, y + mPadding + mSchemeBaseLine, mTextPaint);
}
/**
* 繪制文本
* @param canvas canvas
* @param calendar 日歷calendar
* @param x 日歷Card x起點坐標(biāo)
* @param y 日歷Card y起點坐標(biāo)
* @param hasScheme 是否是標(biāo)記的日期
* @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;
if (isSelected) {//優(yōu)先繪制選擇的
canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
mSelectTextPaint);
canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, mSelectedLunarTextPaint);
} else if (hasScheme) {//否則繪制具有標(biāo)記的
canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
calendar.isCurrentMonth() ? 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() ? mCurMonthTextPaint : mOtherMonthTextPaint);
canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10,
calendar.isCurrentDay() ? mCurDayLunarTextPaint :
calendar.isCurrentMonth() ? mCurMonthLunarTextPaint : mOtherMonthLunarTextPaint);
}
}
/**
* dp轉(zhuǎn)px
*
* @param context context
* @param dpValue dp
* @return px
*/
private static int dipToPx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
實現(xiàn)自定義周視圖,周視圖除了三個回調(diào)函數(shù)少了一個y參數(shù),其它一樣,因為周視圖只有一行,所以可直接copy MonthView的代碼,令y=0即可,如下
public class MeizuWeekView extends WeekView {
private Paint mTextPaint = new Paint();
private Paint mSchemeBasicPaint = new Paint();
private float mRadio;
private int mPadding;
private float mSchemeBaseLine;
public MeizuWeekView(Context context) {
super(context);
mTextPaint.setTextSize(dipToPx(context, 8));
mTextPaint.setColor(0xffffffff);
mTextPaint.setAntiAlias(true);
mTextPaint.setFakeBoldText(true);
mSchemeBasicPaint.setAntiAlias(true);
mSchemeBasicPaint.setStyle(Paint.Style.FILL);
mSchemeBasicPaint.setTextAlign(Paint.Align.CENTER);
mSchemeBasicPaint.setColor(0xffed5353);
mSchemeBasicPaint.setFakeBoldText(true);
mRadio = dipToPx(getContext(), 7);
mPadding = dipToPx(getContext(), 4);
Paint.FontMetrics metrics = mSchemeBasicPaint.getFontMetrics();
mSchemeBaseLine = mRadio - metrics.descent + (metrics.bottom - metrics.top) / 2 + dipToPx(getContext(), 1);
}
/**
*
* @param canvas canvas
* @param calendar 日歷日歷calendar
* @param x 日歷Card x起點坐標(biāo)
* @param hasScheme hasScheme 非標(biāo)記的日期
* @return true 則繪制onDrawScheme,因為這里背景色不是是互斥的
*/
@Override
protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, boolean hasScheme) {
mSelectedPaint.setStyle(Paint.Style.FILL);
mSelectedPaint.setColor(0x80cfcfcf);
canvas.drawRect(x + mPadding, mPadding, x + mItemWidth - mPadding, mItemHeight - mPadding, mSelectedPaint);
return true;
}
@Override
protected void onDrawScheme(Canvas canvas, Calendar calendar, int x) {
mSchemeBasicPaint.setColor(calendar.getSchemeColor());
canvas.drawCircle(x + mItemWidth - mPadding - mRadio / 2, mPadding + mRadio, mRadio, mSchemeBasicPaint);
canvas.drawText(calendar.getScheme(), x + mItemWidth - mPadding - mRadio, mPadding + mSchemeBaseLine, mTextPaint);
}
@Override
protected void onDrawText(Canvas canvas, Calendar calendar, int x, boolean hasScheme, boolean isSelected) {
int cx = x + mItemWidth / 2;
int top = -mItemHeight / 6;
if (isSelected) {
canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
mSelectTextPaint);
canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + mItemHeight / 10, mSelectedLunarTextPaint);
} else if (hasScheme) {
canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
calendar.isCurrentMonth() ? mSchemeTextPaint : mOtherMonthTextPaint);
canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + mItemHeight / 10, mCurMonthLunarTextPaint);
} else {
canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top,
calendar.isCurrentDay() ? mCurDayTextPaint :
calendar.isCurrentMonth() ? mCurMonthTextPaint : mOtherMonthTextPaint);
canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + mItemHeight / 10,
calendar.isCurrentDay() ? mCurDayLunarTextPaint :
calendar.isCurrentMonth() ? mCurMonthLunarTextPaint : mOtherMonthLunarTextPaint);
}
}
/**
* dp轉(zhuǎn)px
*
* @param context context
* @param dpValue dp
* @return px
*/
private static int dipToPx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
最后通過CalendarView兩個attr配置class路徑即可,是不是很像自定義Behavior?
app:month_view="com.haibin.calendarviewproject.meizu.MeiZuMonthView"
app:week_view="com.haibin.calendarviewproject.meizu.MeizuWeekView"
最后各種自定義UI效果自由欣賞,源碼都在demo
color_expand.png
color_shrink.png
index_expand.png
year_view.png
