Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

该仓库代码源自 huanghaibin-dev/CalendarView ,在其代码基础上对周视图、月视图、年视图的绘制逻辑进行了重构。并新增了月视图全屏展开效果的支持,以及新增了月视图切换年视图的动效。

一、快速开始

1.在你项目的 root settings.gradle 添加以下代码:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

2.在模块的gradle中添加依赖:

dependencies {
implementation 'com.github.zhpanvip:CalendarView:Tag'
}

Tag:

3.xml中添加以下代码:

<com.zhangpan.site.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:calendar_content_view_id="@id/scroll_view"
app:layout_constraintTop_toTopOf="parent" >
<com.zhangpan.site.calendarview.CalendarView
android:layout_width="match_parent"android:layout_height="wrap_content"app:calendar_padding="10dp"app:current_day_lunar_text_color="@color/select_rect_background"app:current_day_text_color="@color/select_rect_background"app:current_month_lunar_text_color="@color/black"app:current_month_text_color="@color/black"app:day_text_size="19dp"app:lunar_text_size="10dp"app:max_year="2099"app:min_year="1900"app:month_view_expandable="true"app:month_view_show_mode="mode_only_current"app:other_month_lunar_text_color="@color/black"app:other_month_text_color="@color/black"app:selected_lunar_text_color="@color/white"app:selected_text_color="@color/white"app:selected_theme_color="@color/select_rect_background"app:week_background="@color/white"app:week_bar_height="@dimen/calendar_week_bar_height"app:week_item_height="@dimen/calendar_item_height"app:week_start_with="mon"app:week_text_color="@color/black"app:week_text_size="14dp"app:week_view_scrollable="true"app:weekend_text_color="@color/weekend_text_color"app:year_view_current_day_text_color="@color/select_rect_background"app:year_view_day_text_color="?attr/colorOnSurface"app:year_view_day_text_size="10dp"app:year_view_month_text_color="?attr/colorOnSurface"app:year_view_week_height="14dp"app:year_view_week_text_color="#3E3E3E"app:year_view_week_text_size="10dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"android:background="#4c4f76"android:layout_width="match_parent"android:layout_height="match_parent"/>
</com.zhangpan.site.calendarview.CalendarLayout>

通过以上代码可以实现默认的日历视图效果,效果如下:

calendar

二、自定义样式

CalendarView 提供了强大的扩展能力,你可以通过继承 BaseCalendarDrawer 实现任意的日历样式,可以通过继承

1.自定义周视图/月视图/月视图展开效果

你可以通过继承 BaseCalendarDrawer,来绘制任意样式的日历效果。BaseCalendarDrawer 承担了周视图、月视图以及月视图展开动效的绘制逻辑。下面代码展示了如何自定义 CalendarDrawer:

classCustomCalendarDrawer(privatevarcontext:Context, delegate:CalendarViewDelegate) :
BaseCalendarDrawer(context, delegate) {
companionobject {
privateconstvalDAY_TYPE_HOLIDAY="1"privateconstvalDAY_TYPE_WORK="2"privateconstvalELLIPSIS=""/** * 30% 透明度*/privateconstvalALPHA_30=76
}
privatevar dp1 = context.resources.getDimension(R.dimen.dp_1)
privatevar dp2 = context.resources.getDimension(R.dimen.dp_2)
privatevar dp3 = context.resources.getDimension(R.dimen.dp_3)
privatevar dp4 = context.resources.getDimension(R.dimen.dp_4)
privatevar dp8 = context.resources.getDimension(R.dimen.dp_8)
privatevar dp9 = context.resources.getDimension(R.dimen.dp_9)
privatevar dp10 = context.resources.getDimension(R.dimen.dp_10)
/** * 用于绘制周数与周分隔线*/privateval mWeekPaint:Paint/** * 绘制“班”“休”画笔*/privateval mMarkPaint =Paint()
privateval mSchemePadding:Float= dpToPx(context, 9f)
privateval rectF =RectF()
privateval rectRadius =
context.resources.getDimension(R.dimen.fd_sys_radius_corner_smooth_mini)
// private val workDayBitmap: Bitmap// private val whiteWorkDayBitmap: Bitmap// private val holidayBitmap: Bitmap// private val whiteHolidayBitmap: Bitmapprivateval eventRectPaint:Paintprivateval eventTextPaint:TextPaintprivateval eventMarkRectWidth = dp8
privateval eventMarkRectHeight = dp3
privateval eventMarkRectColor:Int= getColor(R.color.event_circle_color)
privatevar eventMarkRectBottomPadding =0Fprivateval mTodayNumberColor:Int= getColor(R.color.white)
privateval mIsChineseLanguage:Booleanprivateval weekLineTextSize:Intprivateval weekLineTextSizeMarginStart:Floatprivateval weekLineMarginStart:Floatprivateval weekLineSolidColor = getColor(R.color.week_line_solid)
privateval eventMarkRadius = dp8
privateval eventViewRadius = dp3
privatevar textMarginTop =0fprivatevar eventRectTopMargin =0fprivateval eventViewHeight = dpToPx(context, 17f)
privateval mPadding = dp2
privateval expandSelectHeight = dpToPx(context, 49f)
privateval weekLineTextColor:Int= getColor(R.color.week_line_text_color)
privateval eventTextColor = getColor(R.color.black)
privateval selectedDayBackgroundColor = getColor(R.color.select_rect_background)
privatevar canShowEventSize:Int=0init {
val resources = context.resources
weekLineTextSize = resources.getDimensionPixelSize(R.dimen.week_line_text_size)
weekLineTextSizeMarginStart = resources.getDimension(R.dimen.week_line_text_margin_start)
weekLineMarginStart = resources.getDimension(R.dimen.week_line_margin_start)
eventRectTopMargin = dp2
eventRectPaint =Paint()
eventRectPaint.isAntiAlias =true
eventTextPaint =TextPaint()
eventTextPaint.textSize = dp10
eventTextPaint.color = eventTextColor
eventTextPaint.isFakeBoldText =false
eventTextPaint.textAlign =Paint.Align.LEFT
eventTextPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL))
mMarkPaint.textSize = dp8
mMarkPaint.isAntiAlias =true
mMarkPaint.isFakeBoldText =true
mWeekPaint =Paint()
mWeekPaint.isAntiAlias =true
mWeekPaint.textSize = weekLineTextSize.toFloat()
mWeekPaint.strokeWidth = resources.getDimension(R.dimen.calendar_line_stroke_width)
mIsChineseLanguage =trueif (mIsChineseLanguage) {
eventMarkRectBottomPadding = dp9
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top_cn)
} else {
textMarginTop = context.resources.getDimension(R.dimen.calendar_item_text_margin_top)
eventMarkRectBottomPadding = dpToPx(context, 15f)
}
}
overridefunonPreviewDraw(canvas:Canvas?, lineCount:Int) {
super.onPreviewDraw(canvas, lineCount)
canShowEventSize = canShowMaxEventSize(lineCount)
}
/** * 绘制选中日期背景 * @param canvas canvas * @param calendarDay 日历日历calendar * @param x 日历Card x起点rr坐标 * @param y 日历Card y起点坐标 * @param hasScheme hasScheme 非标记的日期 * @return true 则绘制onDrawScheme,因为这里背景色不是是互斥的*/overridefunonDrawSelected(
canvas:Canvas,
calendarDay:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean
): Boolean {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val calendarItemHeight = delegate.calendarItemHeight - dp2
val paddingTop = (delegate.calendarItemHeight - calendarItemHeight + dp1) /2fval height =
(expandSelectHeight - calendarItemHeight) * monthViewExpandPercent + calendarItemHeight
rectF.set(
(x + mPadding),
y.toFloat() + paddingTop,
(x + mItemWidth - mPadding),
(y + height)
)
if (calendarDay.isCurrentDay) {
mSelectedPaint.color = delegate.selectedThemeColor
} else {
mSelectedPaint.color = selectedDayBackgroundColor
}
canvas.drawRoundRect(rectF, rectRadius, rectRadius, mSelectedPaint)
returntrue
}
/** * 绘制标记,如 “班” “休” * @param canvas canvas * @param isSelected 是否选中 * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标*/overridefunonDrawScheme(
canvas:Canvas,
isSelected:Boolean,
calendar:CalendarDay,
x:Int,
y:Int
) {
if (mIsChineseLanguage) {
// if (DAY_TYPE_HOLIDAY == calendar.scheme) {// var bitmap = holidayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteHolidayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// } else if (DAY_TYPE_WORK == calendar.scheme) {// var bitmap = workDayBitmap// if (calendar.isCurrentDay && isSelected) {// bitmap = whiteWorkDayBitmap// }// canvas.drawBitmap(bitmap, x + mItemWidth - mPadding - mSchemePadding - bitmap.width / 2f, y + mPadding + mSchemePadding,// mMarkPaint)// }
}
}
/** * 绘制日期、农历日期、节日等文本 * @param canvas canvas * @param calendar 日历calendar * @param x 日历Card x起点坐标 * @param y 日历Card y起点坐标 * @param hasScheme 是否是标记的日期 * @param isSelected 是否选中*/overridefunonDrawText(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
hasScheme:Boolean,
isSelected:Boolean
) {
val monthTextPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
val metrics = monthTextPaint.fontMetrics
val height = metrics.descent - metrics.ascent
val dateTextBaseLine = (metrics.ascent + metrics.descent) /2+ height + textMarginTop
val dateTextBaseLineY = y + dateTextBaseLine
val lunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
val fontMetrics = lunarPaint.fontMetrics
val lunarTextBaseLine = (fontMetrics.ascent + fontMetrics.descent) /2val lunarTextBaseLineY = y + expandSelectHeight + lunarTextBaseLine - dp4
val cx = x + mItemWidth /2fval isInRange = isInRange(calendar)
// 绘制日期if (isSelected) {
val selectedPaint = delegate.switchTextPaint(BaseView.PAINT_CURRENT_SELECT_DATE)
canvas.drawText(
calendar.day.toString(), cx, dateTextBaseLineY,
selectedPaint
)
} else {
val datePaint:Paint=if (calendar.isCurrentDay) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_DATE)
} elseif (calendar.isCurrentMonth && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_DATE)
}
canvas.drawText(calendar.day.toString(), cx, dateTextBaseLineY, datePaint)
}
// 绘制农历日期、节日if (mIsChineseLanguage) {
val lunarText =if (delegate.monthViewExpandPercent >0.6) {
calendar.lunar
} else {
calendar.lunarText
}
if (isSelected) {
val selectLunarPaint = delegate.switchTextPaint(BaseView.PAINT_SELECT_LUNAR_DATE)
canvas.drawText(lunarText, cx, lunarTextBaseLineY, selectLunarPaint)
} else {
val paint =if (calendar.isCurrentDay && isInRange) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_DAY_LUNAR_DATE)
} elseif (calendar.isWeekend) {
delegate.switchTextPaint(BaseView.PAINT_WEEKEND_LUNAR_DATE)
} elseif (calendar.isCurrentMonth) {
delegate.switchTextPaint(BaseView.PAINT_CURRENT_MONTH_LUNAR_DATE)
} else {
delegate.switchTextPaint(BaseView.PAINT_OTHER_MONTH_LUNAR_DATE)
}
canvas.drawText(lunarText, cx, lunarTextBaseLineY, paint)
}
}
startDrawEvent(canvas, calendar, x, y, isSelected)
}
/** * 绘制日历事件*/privatefunstartDrawEvent(
canvas:Canvas,
calendar:CalendarDay,
x:Int,
y:Int,
isSelected:Boolean
) {
val calendarEvents = calendar.events
if (calendarEvents !=null&& calendarEvents.isNotEmpty()) {
val calendarInitHeight = delegate.calendarItemHeight
val originBottom = y + calendarInitHeight - eventMarkRectBottomPadding
val originTop = originBottom - eventMarkRectHeight
val eventMarkColor =if (isSelected) {
mTodayNumberColor
} else {
eventMarkRectColor
}
if (delegate.monthViewExpandPercent <=0f) {
val curLeft = x + mItemWidth /2f- eventMarkRectWidth /2fval curRight = x + mItemWidth /2f+ eventMarkRectWidth /2f
eventRectPaint.color = eventMarkColor
rectF.set(curLeft, originTop, curRight, originBottom)
canvas.drawRoundRect(rectF, eventMarkRadius, eventMarkRadius, eventRectPaint)
} else {
calendarEvents.forEachIndexed { index, calendarEvent ->if (index < canShowEventSize) {
drawEventRect(canvas, x, y, index, calendarEvent, originTop, eventMarkColor)
}
}
}
}
}
privatefundrawEventRect(
canvas:Canvas,
x:Int,
y:Int,
index:Int,
calendarEvent:CalendarDay.CalendarEvent,
originTop:Float,
startColor:Int
) {
val monthViewExpandPercent = delegate.monthViewExpandPercent
val maxWidth = mItemWidth - dp2
val curWidth = (maxWidth - eventMarkRectWidth) * monthViewExpandPercent + eventMarkRectWidth
// event rect horizontal marginval marginHorizontal = (mItemWidth - curWidth) /2fval endTop =
y + expandSelectHeight + index * (eventViewHeight + eventRectTopMargin) + eventRectTopMargin
val curTop = (endTop - originTop) * monthViewExpandPercent + originTop
val curBottom =
curTop + (eventViewHeight - eventMarkRectHeight) * monthViewExpandPercent + eventMarkRectHeight
rectF.set(x + marginHorizontal, curTop, x + mItemWidth - marginHorizontal, curBottom)
eventRectPaint.setColor(
interpolateColors(
monthViewExpandPercent,
startColor,
calendarEvent.eventColor
)
)
canvas.drawRoundRect(rectF, eventViewRadius, eventViewRadius, eventRectPaint)
if (monthViewExpandPercent >0.1) {
eventTextPaint.textSize = dp9 * monthViewExpandPercent
val fm = eventTextPaint.getFontMetrics()
val height = fm.descent - fm.ascent
val textWithEllipsis =
getTextWithEllipsis(calendarEvent.eventName, eventTextPaint, curWidth)
val offset = dpToPx(context, 2* monthViewExpandPercent)
canvas.drawText(
textWithEllipsis,
x + marginHorizontal + offset,
curTop + ((curBottom - curTop) + height) /2- offset,
eventTextPaint
)
}
}
overridefunonStartDrawWeek(
canvas:Canvas,
weekStartDay:CalendarDay,
lineNum:Int,
itemHeight:Int
) {
super.onStartDrawWeek(canvas, weekStartDay, lineNum, itemHeight)
mItemHeight = itemHeight
val weekNum = weekStartDay.weekNum
if (delegate.isShowWeekNum) {
mWeekPaint.color = weekLineSolidColor
canvas.drawLine(
weekLineMarginStart,
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mLineWidth.toFloat(),
(lineNum * itemHeight).toFloat() + delegate.calendarPaddingTop,
mWeekPaint
)
mWeekPaint.color =if (weekStartDay.isCurrentWeek) {
delegate.selectedThemeColor
} else {
weekLineTextColor
}
canvas.drawText(
weekNum.toString(),
weekLineTextSizeMarginStart,
(lineNum * itemHeight + weekLineTextSize /2.3f) + delegate.calendarPaddingTop,
mWeekPaint
)
}
}
/** * 计算最多可以显示几条日程*/privatefuncanShowMaxEventSize(lineCount:Int): Int {
returnif (lineCount ==0) {
((mItemHeight - expandSelectHeight) / eventViewHeight).toInt()
} else {
val maxLineHeight = (delegate.monthViewExpandHeight / lineCount *1F)
((maxLineHeight - expandSelectHeight) / (eventViewHeight + eventRectTopMargin)).toInt()
}
}
privatefuninterpolateColors(fraction:Float, startColor:Int, endColor:Int): Int {
val startA =Color.alpha(startColor)
val startR =Color.red(startColor)
val startG =Color.green(startColor)
val startB =Color.blue(startColor)
val endA =ALPHA_30val endR =Color.red(endColor)
val endG =Color.green(endColor)
val endB =Color.blue(endColor)
val finalA = startA + (fraction * (endA - startA)).toInt()
val finalR = startR + (fraction * (endR - startR)).toInt()
val finalG = startG + (fraction * (endG - startG)).toInt()
val finalB = startB + (fraction * (endB - startB)).toInt()
returnColor.argb(finalA, finalR, finalG, finalB)
}
privatefungetColor(color:Int): Int {
returnContextCompat.getColor(mContext, color)
}
privatefungetTextWithEllipsis(text:String, paint:TextPaint, width:Float): String {
val textWidth = paint.measureText(text)
if (textWidth <= width) {
return text
} else {
val ellipsisWidth = paint.measureText(ELLIPSIS)
val availableWidth = (width - ellipsisWidth).toInt()
var availableLength =0for (i in text.indices) {
val charWidth = paint.measureText(text.substring(0, i +1))
if (charWidth <= availableWidth) {
availableLength = i +1
} else {
break
}
}
return text.substring(0, availableLength) +ELLIPSIS
}
}
}

完成自定义 CalendarDrawer 后在 xml 文件中给 CalendarView 添加自定义属性 app:calendar_drawer="com.zhangpan.site.demo.calendarview.CustomCalendarDrawer" 即可。

2.自定义年视图样式

通过继承 BaseYearView 来绘制需要的年视图样式,示例代码如下:

class CustomYearView(context: Context) : BaseYearView(context) {
private var currentYear = 0
private var currentMonth = 0
private var rectWidth = CalendarUtil.dipToPx(context, 6f)
private var rectHeight = CalendarUtil.dipToPx(context, 1f)
private var rect = Rect()
private var monthArray: Array<String>
init {
val instance = Calendar.getInstance()
currentYear = instance.get(Calendar.YEAR)
currentMonth = instance.get(Calendar.MONTH) + 1
monthArray = getContext().resources.getStringArray(R.array.lunar_first_of_month)
}
private val mTextPadding = CalendarUtil.dipToPx(context, 3f)
override fun onDrawMonth(
canvas: Canvas,
year: Int,
month: Int,
x: Int,
y: Int,
width: Int,
height: Int
) {
val text = context
.resources
.getStringArray(R.array.month_string_array)[month - 1]
if (currentMonth == month && currentYear == year) {
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
} else {
mMonthTextPaint.color = mDelegate.yearViewMonthTextColor
}
canvas.drawText(
text,
x + mItemWidth / 2f - mTextPadding,
y + mMonthTextBaseLine,
mMonthTextPaint
)
}
override fun onDrawWeek(canvas: Canvas, week: Int, x: Int, y: Int, width: Int, height: Int) {
val text = context.resources.getStringArray(R.array.year_view_week_string_array)[week]
canvas.drawText(
text,
x + width / 2f,
y + mWeekTextBaseLine,
mWeekTextPaint
)
}
override fun onDrawSelected(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean
): Boolean {
Log.d("onDrawSelected", "CalendarDay:${calendar.year} ${calendar.month} ${calendar.day}")
return false
}
override fun onDrawScheme(canvas: Canvas, calendar: CalendarDay, x: Int, y: Int) {
}
override fun onDrawText(
canvas: Canvas,
calendar: CalendarDay,
x: Int,
y: Int,
hasScheme: Boolean,
isSelected: Boolean
) {
val baselineY = mTextBaseLine + y
val cx = x + mItemWidth / 2
canvas.drawText(
calendar.day.toString(), cx.toFloat(), baselineY,
if (calendar.isCurrentDay) mCurDayTextPaint else mCurMonthTextPaint
)
if (isLunarDay1(calendar.lunar)) {
val start = x + (mItemWidth - rectWidth) / 2
val top = y + mItemHeight - rectHeight
var bottom = top + rectHeight
if (monthArray.firstOrNull() == calendar.lunar) {
bottom = top + (rectHeight * 2)
}
rect.set(start, top, start + rectWidth, bottom)
mMonthTextPaint.color = mDelegate.yearViewCurDayTextColor
canvas.drawRect(rect, mMonthTextPaint)
}
}
private fun isLunarDay1(lunar: String): Boolean {
return monthArray.contains(lunar)
}
}

About

📅n elegant Android calendar view library with customizable month/week/year views, smooth expand animation, and system calendar integration. Based on CalendarView with refactored drawing logic.一个优雅的 Android 日历视图库,支持自定义月/周/年视图、月视图展开动效、年视图切换动画,以及系统日历日程集成。基于 CalendarView 重构绘制逻辑。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages