Android精美日曆控件CalendarView自定義使用徹底解析

項目github地址

https://github.com/huanghaibin-dev/CalendarViewjava

此框架採用組合的方式,各個模塊互相獨立,可自由採用各類提供的控件組合,徹底自定義本身須要的UI,周視圖和月視圖可經過簡單自定義任意自由繪製,不怕美工提需求!!!下面教程將介紹如何實現3個API,自定義Canvas繪製日曆

CalendarView的優點:

一、熱插拔設計,根據不一樣的UI需求徹底自定義UI,簡單幾步便可實現,自定義事件日曆標記、顏色、農曆等

二、徹底Canvas繪製,性能和速度都很不錯,相比大多數基於GridView或RecyclerView實現的佔用內存更低,啓動速度更快

三、支持收縮、展開、快速年月份選擇等

四、簡潔易懂的源碼,易學習。

Gradle

compile 'com.haibin:calendarview:3.2.9'
複製代碼
<dependency>
  <groupId>com.haibin</groupId>
  <artifactId>calendarview</artifactId>
  <version>3.2.9</version>
  <type>pom</type>
</dependency>
複製代碼

混淆proguard-rules

-keepclasseswithmembers class * {
    public <init>(android.content.Context);
}
複製代碼

國際慣例,先上一個自定義效果圖,結尾還有幾個其它效果圖,可本身自定義

     

各個類功能介紹

CalendarLayout

這是個輔助類,負責CalendarView的收縮控制功能,若是不須要收縮功能,無需使用它,通常使用教程以下android

<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:week_start_with="mon" 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();//是否已經展開
複製代碼
<!-- 日曆顯示模式 -->
<attr name="calendar_show_mode">
      <enum name="both_month_week_view" value="0" /><!-- 默認都有 -->
      <enum name="only_week_view" value="1" /><!-- 僅周視圖 -->
      <enum name="only_month_view" value="2" /><!-- 僅月視圖 -->
</attr>

<attr name="default_status">
      <enum name="expand" value="0" /> <!--默認展開-->
      <enum name="shrink" value="1" /><!--默認搜索-->
</attr>

<attr name="calendar_content_view_id" format="integer" /><!--內容佈局id-->

複製代碼
CalendarView

真正的日曆類,能夠自行經過attr配置git

<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" /><!--標記的文本,通常可忽略-->
        <attr name="day_text_size" format="dimension" /><!--日期字體大小-->
        <attr name="lunar_text_size" format="dimension" /><!--農曆字體大小-->
        <attr name="calendar_height" format="dimension" /><!--日曆卡的高度,每一項,不是總體-->
        <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_day_text_color" format="color" /><!--今天的日子字體顏色-->
        <attr name="current_day_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="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" /><!--年視圖標記字體顏色-->
        <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" /><!--最小年份對應最小月份-->
        <attr name="max_year_month" format="integer" /><!--最大年份對應月份-->
        
        <!--配置你喜歡的月視圖顯示模式模式-->
        <attr name="month_view_show_mode">
             <enum name="mode_all" value="0" /> <!--所有顯示-->
             <enum name="mode_only_current" value="1" /> <!--僅顯示當前月份-->
             <enum name="mode_fix" value="2" /> <!--自適應顯示,不會多出一行,可是會自動填充-->
        </attr>
        
         <!-- 自定義周起始 -->
        <attr name="week_start_with">
             <enum name="sun" value="1" />
             <enum name="mon" value="2" />
             <enum name="sat" value="7" />
        </attr>
    </declare-styleable>
複製代碼

CalendarView api

public int getCurDay(); //今天
public int getCurMonth(); //當前的月份
public int getCurYear(); //今年
public void showSelectLayout(final int year); //快速彈出年份選擇月份
public void closeSelectLayout(final int position); //關閉選擇年份並跳轉日期

/** * 設置日期範圍 * * @param minYear 最小年份 * @param minYearMonth 最小年份對應月份 * @param maxYear 最大月份 * @param maxYearMonth 最大月份對應月份 */
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);//標記日期

public void update();//動態更新

public Calendar getSelectedCalendar(); //獲取選擇的日期

public void scrollToPre();//滾動到上一個月

public void scrollToNext();//滾動到下一個月

public void scrollToCalendar(int year, int month, int day);//滾動到指定日期

/** * 設置背景色 * * @param monthLayoutBackground 月份卡片的背景色 * @param weekBackground 星期欄背景色 * @param lineBg 線的顏色 */
public void setBackground(int monthLayoutBackground, int weekBackground, int lineBg) /** * 設置文本顏色 * * @param curMonthTextColor 當前月份字體顏色 * @param otherMonthColor 其它月份字體顏色 * @param lunarTextColor 農曆字體顏色 */ public void setTextColor(int curMonthTextColor,int otherMonthColor,int lunarTextColor) /** * 設置選擇的效果 * * @param style 選中的style CalendarCardView.STYLE_FILL or CalendarCardView.STYLE_STROKE * @param selectedThemeColor 選中的標記顏色 * @param selectedTextColor 選中的字體顏色 */ public void setSelectedColor(int style, int selectedThemeColor, int selectedTextColor) /** * 設置標記的色 * * @param style 標記的style CalendarCardView.STYLE_FILL or CalendarCardView.STYLE_STROKE * @param schemeColor 標記背景色 * @param schemeTextColor 標記字體顏色 */ public void setSchemeColor(int style, int schemeColor, int schemeTextColor) /** * 設置背景色 * * @param yearViewBackground 年份卡片的背景色 * @param weekBackground 星期欄背景色 * @param lineBg 線的顏色 */ public void setBackground(int yearViewBackground, int weekBackground, int lineBg) 複製代碼

這個控件的特別之處就是它的UI是能夠交給客戶端自由繪製的,所以能夠自由發揮想象力,繪製你須要的日曆效果UI

接下來介紹如何徹底自定義日曆,自定義日曆須要同時自定義月視圖和周視圖,代碼幾乎同樣,須要實現三個回調函數便可,以下:

onDrawSelected
onDrawScheme
onDrawText
複製代碼
/** * 首先繼承月視圖,假如咱們想實現高仿魅族的日曆 */

public class MeiZuMonthView extends MonthView {

    /** * 自定義魅族標記的文本畫筆 */
    private Paint mTextPaint = new Paint();

    /** * 自定義魅族標記的圓形背景 */
    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起點座標 * @param y 日曆Card y起點座標 * @param hasScheme hasScheme 非標記的日期 * @return 返回true 則會繼續繪製onDrawScheme,由於這裏背景色不是是互斥的,因此返回true,返回false,則點擊scheme標記的日子,則不繼續繪製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;
    }

    /** * 繪製標記的事件日子 * @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) {
        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起點座標 * @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 (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() ? 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轉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);
    }
}

複製代碼

實現自定義周視圖,周視圖除了三個回調函數少了一個y參數,其它同樣,由於周視圖只有一行,因此可直接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起點座標 * @param hasScheme hasScheme 非標記的日期 * @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轉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

     

     

     

     

建議clone APP Demo,裏面做者實現了幾種類型的風格,能夠參考實現

項目開源地址,一點貢獻

https://github.com/huanghaibin-dev/CalendarViewgithub

相關文章
相關標籤/搜索