Android開源的精美日歷控件,熱插拔設計的萬能自定義UI
UI框架應該邏輯與界面實現分離,該日歷控件使用了熱插拔的設計 ,簡單幾步即可實現你需要的UI效果,熱插拔的思想是你提供你的實現,我提供我的插座接口,與自定義Behavior是一樣的思想。
CalendarView的優勢:
1、熱插拔設計,根據不同的UI需求完全自定義UI,簡單幾步即可實現,自定義事件日歷標記、顏色、農歷等
2、完全Canvas繪制,性能和速度都很不錯,相比大多數基於GridView或RecyclerView實現的占用內存更低,啟動速度更快
3、支持收縮、展開、快速年月份選擇等
4、簡潔易懂的源碼,易學習。
先看看控件的attr
<declare-styleable name="CalendarView"> <attr name="calendar_card_view" format="color" /> <!--熱插拔自定義類路徑--> <attr name="week_background" format="color" /> <!--星期欄的背景--> <attr name="week_text_color" format="color" /> <!--星期欄文本顏色--> <attr name="current_day_text_color" format="color" /> <!--今天的文本顏色--> <attr name="day_text_size" format="string" /> <!--天數文本大小--> <attr name="lunar_text_size" format="string" /> <!--農歷文本大小--> <attr name="scheme_text" format="string" /> <!--標記文本--> <attr name="scheme_text_color" format="color" /> <!--標記文本顏色--> <attr name="scheme_month_text_color" format="color" /> <!--標記天數文本顏色--> <attr name="scheme_lunar_text_color" format="color" /> <!--標記農歷文本顏色--> <attr name="scheme_theme_color" format="color" /> <!--標記的顏色--> <attr name="selected_theme_color" format="color" /> <!--選中顏色--> <attr name="selected_text_color" format="color" /> <!--選中文本顏色--> <attr name="selected_lunar_text_color" format="color" /> <!--選中農歷文本顏色--> <attr name="current_month_text_color" format="color" /> <!--當前月份的字體顏色--> <attr name="other_month_text_color" format="color" /> <!--其它月份的字體顏色--> <attr name="current_month_lunar_text_color" format="color" /> <!--當前月份農歷節假日顏色--> <attr name="other_month_lunar_text_color" format="color" /> <!--其它月份農歷節假日顏色--> <attr name="min_year" format="integer" /> <!--最小年份1900--> <attr name="max_year" format="integer" /> <!--最大年份2099--> </declare-styleable>
XML用法
如果需要在日歷控件下方使用其它控件,使用CalendarLayout控件即可,calendar_content_view_id為其它控件的id,支持任意控件,如RecyclerView、ListView。CalendarView的calendar_card_view為任意自定義實現的日歷繪制控件路徑。
<com.haibin.calendarview.CalendarLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#fff" app:calendar_content_view_id="@+id/linearView"> <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:other_month_text_color="#e1e1e1" app:scheme_text_color="#333" app:scheme_theme_color="#128c4b" app:selected_lunar_text_color="#CFCFCF" app:calendar_card_view="com.haibin.calendarviewproject.meizu.MeiZuCalendarCardView" app:selected_text_color="#333" app:selected_theme_color="#108cd4" app:week_background="#fff" app:week_text_color="#111" /> <LinearLayout android:id="@+id/linearView" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/content_background" android:clickable="true" android:orientation="vertical" tools:ignore="KeyboardInaccessibleWidget"/> </LinearLayout> </com.haibin.calendarview.CalendarLayout>
熟悉一下這幾個簡單的特性,看看日歷內容界面的繪制BaseCalendarCardView,根據需求實現以下部分方法即可
/** * 開始繪制前的回調鈎子,這里做一些初始化的操作,每次繪制只調用一次,性能高效 * 沒有需要可忽略不實現 * 例如: * 1、需要繪制圓形標記事件背景,可以在這里計算半徑 * 2、繪制矩形選中效果,也可以在這里計算矩形寬和高 */ protected void onPreviewHook() { // TODO: 2017/11/16 } /** * 循環繪制開始的回調,不需要可忽略 * 繪制每個日歷項的循環,用來計算baseLine、圓心坐標等都可以在這里實現 * * @param x 日歷Card x起點坐標 * @param y 日歷Card y起點坐標 */ protected void onLoopStart(int x, int y) { // TODO: 2017/11/16 } /** * 繪制選中的日期 * * @param canvas canvas * @param calendar 日歷日歷calendar * @param x 日歷Card x起點坐標 * @param y 日歷Card y起點坐標 * @param hasScheme hasScheme 非標記的日期 */ protected abstract void onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme); /** * 繪制標記的日期UI * * @param canvas canvas * @param calendar 日歷calendar * @param x 日歷Card x起點坐標 * @param y 日歷Card y起點坐標 */ protected abstract void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y); /** * 繪制日歷文本 * * @param canvas canvas * @param calendar 日歷calendar * @param x 日歷Card x起點坐標 * @param y 日歷Card y起點坐標 * @param hasScheme 是否是標記的日期 * @param isSelected 是否選中 */ protected abstract void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected);
舉個例子:如果你的需求是類似魅族日歷的UI,那么第一步,繼承BaseCalendarCardView,然后實現onDrawSelected、onDrawScheme、onDrawText三個回調函數即可
public class MeiZuCalendarCardView extends BaseCalendarCardView { private Paint mTextPaint = new Paint(); private Paint mSchemeBasicPaint = new Paint(); private float mRadio; private int mPadding; private float mSchemeBaseLine; public MeiZuCalendarCardView(Context context) { super(context); mTextPaint.setTextSize(dipToPx(context, 8)); mTextPaint.setColor(0xff111111); 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起點坐標 * @param y 日歷Card y起點坐標 * @param hasScheme hasScheme 非標記的日期 */ @Override protected void 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); } /** * 繪制標記的日期UI 這里魅族界面不需要繪制多彩風格,忽略即可 * * @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) { } /** * 繪制日歷文本 * * @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; if (hasScheme) { //繪制日期 canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, calendar.isCurrentDay() ? mCurDayTextPaint : calendar.isCurrentMonth() ? mSchemeTextPaint : mOtherMonthTextPaint); //繪制農歷 canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, mCurMonthLunarTextPaint); mTextPaint.setColor(Color.WHITE); 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); } 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, mCurMonthLunarTextPaint); } } /** * dp轉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); } }
第二步:使用方法、app:calendar_card_view="xxx.xx.MeiZuCalendarCardView"
魅族風格日歷效果預覽

快速年月份選擇

其它作者實現的幾個UI效果預覽,簡單源碼都在demo可以看到
多彩風格界面

下標風格界面

簡單沒有農歷界面

更多參考用法移步APP Demo,里面作者實現了幾種類型的風格,可以參考實現
項目開源地址
https://github.com/huanghaibin-dev/CalendarView
如果覺得源碼可以請給個star,源碼注釋完善,簡單易懂,容易學習。
