知足你各類姿式的最美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地址

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

國內慣例:無圖言吊

     

     

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"git

  • 酒店式日曆場景固然是不能從昨天開始訂房的,也不能無限期訂房,因此你須要靜態或動態設置日曆範圍、精確到具體某一天!!!github

<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 能很是簡單就高仿各類日曆UIcanvas

  • CalendarView 提供了 setSchemeDate(Map<String, Calendar> mSchemeDates) 這個十分高效的API用來動態標記事務,即時你的數據量達到數千、數萬、數十萬,都不會對UI渲染形成影響bash

  • 日曆類 Calendar 提供了許多十分有用的APIapp

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或者留下你寶貴的意見。

博客慣例:結尾再放github地址,否則你就不肯意翻到最上面點擊了

https://github.com/huanghaibin-dev/CalendarView框架

相關文章
相關標籤/搜索