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

本贴最后更新于 2647 天前,其中的信息可能已经时移世异

项目 github 地址

github.com/huanghaibin…

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

CalendarView 的优势:

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

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

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

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

Gradle

compile 'com.haibin:calendarview:3.1.3'
<dependency> <groupId>com.haibin</groupId> <artifactId>calendarview</artifactId> <version>3.1.3</version> <type>pom</type> </dependency>

混淆 proguard-rules

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

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

各个类功能介绍

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: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" /><!--最大年份对应月份--> </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://juejin.im/post/5a6743836fb9a01caa20aefc
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    335 引用 • 324 回帖
  • CalendarView
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...