先上效果图:
这样的日历控件显然已经烂大街都是了,这里小弟只是在这儿练练手,正好大作业也有用到就整理一篇博客,希望留作记录与学习。
完整代码在https://github.com/CarsonWoo/MyCalendarView 有kotlin和java两个版本
首先是绘制分析工作,我们知道这个日历控件我们可以分成几个模块来看。
红色框框和蓝色框框的实现比较容易,只是还要注意蓝色框框内星期的间距:要通过计算一个格子占用的空间占总宽度的1/7.有了这个间距,我们下面的日期也是同理drawText上去就好了。先上前两个框框的实现代码:
private void drawMonth(Canvas canvas) {
mTextPaint.setTextSize(mTitleSize);
mTextPaint.setColor(Color.BLACK);
int x = (getWidth() - (int) mTextPaint.measureText(currentMonth)) / 2;
canvas.drawText(currentMonth, x, mTitleHeight, mTextPaint);
}
private void drawWeek(Canvas canvas) {
mTextPaint.setTextSize(mWeekSize);
mTextPaint.setColor(Color.parseColor("#BBBBBB"));
for (int i = 0; i < 7; i++) {
// 获取星期对应的字段的长度
int len = (int) mTextPaint.measureText(weeks[i]);
// 设置当前从哪个位置开始drawText
int currentX = (int) (i * perWidth + (perWidth - len) / 2);
canvas.drawText(weeks[i], currentX, mTitleHeight
+ mWeekPaddingTop + mWeekHeight, mTextPaint);
}
// 绘制分隔线
canvas.drawLine(0, mTitleHeight
+ mWeekPaddingTop + mWeekHeight + dp2px(10), getMeasuredWidth(),
mTitleHeight + mWeekPaddingTop + mWeekHeight + dp2px(10), mBgPaint);
}
至于perWidth在哪里拿,其实我们只需要在onMeasure的时候初始化一遍就好了。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
perWidth = width / 7;
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), heightMeasureSpec);
}
有了perWidth,我们甚至可以为所欲为。。。(开个玩笑)
其实我上面那句并不完全是玩笑话,毕竟拿到了perWidth,我们只需要在对应的currentX以及currentY下drawText就可以成功的绘制出类日历效果,但是真正的日历还是要通过计算的,所以还是得老老实实地进行计算。
计算的过程其实也比较简单,我们只是需要拿到当前月份的第一天是星期几,并且通过Date日期类去获取到当月有多少天即可。当然我这里还做了上一个月有多少天的一个计算,这里其实可以用map去存储,避免重复计算,是一个优化的点。
public void setMonth(Date date) {
SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");
String mCurDate = sf.format(date);
Log.i(TAG, "mCurDate = " + mCurDate);
String month = mCurDate.substring(4, 6);
Log.i(TAG, "month = " + month);
String curDate = sf.format(new Date());
String curMonth = curDate.substring(4, 6);
if (curMonth.equals(month)) {
isCurMonth = true;
} else {
isCurMonth = false;
}
if (month.startsWith("0")) {
month = month.substring(1);
}
Log.i(TAG, "month = " + month);
int monthInt = Integer.parseInt(month);
this.currentMonth = months[monthInt - 1];
Log.i(TAG, "monthInt = " + monthInt);
String mDate = mCurDate.substring(6, 8);
if (mDate.startsWith("0")) {
mDate = mDate.substring(1);
}
// 获取到当前日期
this.currentDate = Integer.parseInt(mDate);
Calendar c = Calendar.getInstance();
c.setTime(date);
// 当前月份第一天开始是星期几 周一开始算的话要 - 2
firstIndex = c.get(Calendar.DAY_OF_WEEK) - 2;
if (firstIndex < 0) {
firstIndex += 7;
}
Log.i(TAG, "firstIndex = " + firstIndex);
// 当前月份的天数
dayOfMonth = c.getActualMaximum(Calendar.DAY_OF_MONTH);
int days = dayOfMonth;
Log.i(TAG, "dayOfMonth = " + dayOfMonth);
days -= (7 - firstIndex);
// 最后一行剩余的天数
remains = days;
rowCount = 1;
while (remains >= 7) {
remains -= 7;
rowCount++;
}
if (remains > 0) rowCount++;
Log.i(TAG, "rowCount = " + rowCount);
Log.i(TAG, "remains = " + remains);
// 模拟添加数据
selectedDates.add(4);
selectedDates.add(9);
selectedDates.add(16);
selectedDates.add(25);
// 上一个月
c.add(Calendar.MONTH, -1);
// 上一个月总共有多少天
lastMonthDays = c.getActualMaximum(Calendar.DAY_OF_MONTH);
}
至于drawDate,那还不是分分钟的事吗。这里提示一下:想要做阴影效果还是要关闭硬件加速才可以显示。
如果仔细研究代码可以发现我这里的cy是没有拿到准确值的,我也是根据视觉去调整。我也会好好思考一下这里怎么去拿到准确值。也希望有大佬能和我分享一下。