美文网首页Android自定义控件技术干货掘金
Android精美日历控件CalendarView自定义使用完全

Android精美日历控件CalendarView自定义使用完全

作者: 黄海彬 | 来源:发表于2018-01-29 17:34 被阅读525次

    项目github地址

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

    此框架采用组合的方式,各个模块互相独立,可自由采用各种提供的控件组合,完全自定义自己需要的UI,周视图和月视图可通过简单自定义任意自由绘制,不怕美工提需求!!!下面教程将介绍如何实现3个API,自定义Canvas绘制日历

    CalendarView的优势:

    1、热插拔设计,根据不同的UI需求完全自定义UI,简单几步即可实现,自定义事件日历标记、颜色、农历等

    2、完全Canvas绘制,性能和速度都很不错,相比大多数基于GridView或RecyclerView实现的占用内存更低,启动速度更快

    3、支持收缩、展开、快速年月份选择等

    4、不要再问可不可以不显示其它月份的日期,可以!!!,你可以任意配置到你喜欢为止;所以也不要问其它什么周末可不可以显示灰色,其它什么可不可以,只要你稍微懂Canvas的API,这个控件真的可以!!!,UI是万能绘制的

    5、简洁易懂的源码,易学习。

    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);
    }
    

    国际惯例,先上一个自定义效果图,结尾还有几个其它效果图,可自己自定义

    meizu.png
    meizu_shrink.png

    各个类功能介绍

    CalendarLayout

    这是个辅助类,负责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();//是否已经展开
    
     <declare-styleable name="CalendarLayout">
            <attr name="default_status"> <!--默认状态-->
                <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" /><!--如果需要放内容,请标记子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" /><!--标记的文本,一般可忽略-->
            <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>
        </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

    color_expand.png
    color_shrink.png

    index_expand.png
    year_view.png

    ![simple_shrink.png](http:https://img.haomeiwen.com/i1509545/0c19422c5049d601.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800)

    建议clone APP Demo,里面作者实现了几种类型的风格,可以参考实现

    项目开源地址,一点贡献

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

    相关文章

      网友评论

      • cz小安卓:请问,这个年视图在哪设置?还是说要自己实现

      本文标题:Android精美日历控件CalendarView自定义使用完全

      本文链接:https://www.haomeiwen.com/subject/iccfzxtx.html