推荐一篇讲解CalendarView特别好的博客:https://blog.csdn.net/huanghaibin_dev/article/details/79040147
我的这篇文章重点不是讲解CalendarView怎么使用的,是说我在使用过程中,由于自己脑抽,本来很容易的事情,被自己搞复杂了。尤其是下文第3点,到现在都怀疑当时的自己怎么看资料的。怎么修改“时间选择范围”背景色、结束日期小于开始日期依旧绘制及处理逻辑、根据项目需求实时显示当前年月……,以达到UI效果
一:基本属性的使用:
1.日历显示模式有三种(由CalendarLayout控制,直接在布局里面设置,eg:app:calendar_show_mode="both_month_week_view ",):
both_month_week_view //月模式和周模式(默认模式,可切换月周模式)
only_week_view //周模式
only_month_view //月模式
2.设置日历间距calendarView.setCalendarItemHeight
3.想要日历月周模式切换时,下面(指日历下面的布局)的内容跟着上下走动。这个梗,一开始接触这个日历时,研究了好久,一直没有做出来,主要是:
1)没有设置calendar_show_mode属性为both_month_week_view;
2)一直误认为com.haibin.calendarviewproject.group.GroupRecyclerView才是真正实现,下面跟随日历上下收缩的关键。其实项目的要求是,日历下面跟随着一个Webview,这个Webview跟随日历收缩而进行上下。到了某天,突然间理解了
以WebView为例,重点是:
(1)在CalendarLayout设置app:calendar_content_view_id="@+id/recyclerView"这个属性,recyclerView就是WebView的id;
(2)WebView必须在CalendarView后面:
CustomRangeMonthView.java(CustomRangeWeekView.java同理)代码改成如下:
private int mRadius;
private int middleColor = CalendarColors.middleColor;//中间范围背景色
private int selectedColor = CalendarColors.selectedColor;//选择的背景色
private int middleTxtColor = CalendarColors.middleTxtColor;//中间范围字体颜色
private int selectedTxtColor = CalendarColors.selectedTxtColor;//选择字体颜色
public CustomRangeMonthView(Context context) {
super(context);
}
@Override
protected void onPreviewHook() {
mRadius = Math.min(mItemWidth, mItemHeight) / 5 * 2;
mSchemePaint.setStyle(Paint.Style.STROKE);
}
/**
* 绘制选中的日期
*
* @param canvas canvas
* @param calendar 日历日历calendar
* @param x 日历Card x起点坐标
* @param y 日历Card y起点坐标
* @param hasScheme hasScheme 非标记的日期
* @param isSelectedPre 上一个日期是否选中
* @param isSelectedNext 下一个日期是否选中
* @return 是否继续绘制onDrawScheme,true or false
*/
@Override
protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme,
boolean isSelectedPre, boolean isSelectedNext) {
int cx = x + mItemWidth / 2;
int cy = y + mItemHeight / 2;
if (isSelectedPre) {
if (isSelectedNext) {// 中间日期
mSelectedPaint.setColor(middleColor);
canvas.drawRect(x, cy - mRadius, x + mItemWidth, cy + mRadius, mSelectedPaint);
} else {//最后一个,the last 最后一个日期
mSelectedPaint.setColor(middleColor);
canvas.drawRect(x, cy - mRadius, cx, cy + mRadius, mSelectedPaint);
mSelectedPaint.setColor(selectedColor);
canvas.drawCircle(cx, cy, mRadius, mSelectedPaint);
}
} else {
if(isSelectedNext){
mSelectedPaint.setColor(middleColor);
canvas.drawRect(cx, cy - mRadius, x + mItemWidth, cy + mRadius, mSelectedPaint);
}
mSelectedPaint.setColor(selectedColor);
canvas.drawCircle(cx, cy, mRadius, mSelectedPaint);
}
return false;
}
@Override
protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y, boolean isSelected) {
int cx = x + mItemWidth / 2;
int cy = y + mItemHeight / 2;
canvas.drawCircle(cx, cy, mRadius, mSchemePaint);
}
@Override
protected void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected) {
float baselineY = mTextBaseLine + y;
int cx = x + mItemWidth / 2;
boolean isInRange = isInRange(calendar);
boolean isEnable = !onCalendarIntercept(calendar);
if (isSelected) {
boolean isSelectedPre = isSelectPreCalendar(calendar);
boolean isSelectedNext = isSelectNextCalendar(calendar);
if (isSelectedPre) {
if (isSelectedNext) {// 中间日期
mSelectTextPaint.setColor(middleTxtColor);
} else {//最后一个,the last 最后一个日期
mSelectTextPaint.setColor(selectedTxtColor);
}
} else {
if(isSelectedNext){
}
mSelectTextPaint.setColor(selectedTxtColor);
}
canvas.drawText(String.valueOf(calendar.getDay()),
cx,
baselineY,
mSelectTextPaint);
} else if (hasScheme) {
canvas.drawText(String.valueOf(calendar.getDay()),
cx,
baselineY,
calendar.isCurrentDay() ? mCurDayTextPaint :
calendar.isCurrentMonth() && isInRange && isEnable? mSchemeTextPaint : mOtherMonthTextPaint);
} else {
canvas.drawText(String.valueOf(calendar.getDay()), cx, baselineY,
calendar.isCurrentDay() ? mCurDayTextPaint :
calendar.isCurrentMonth() && isInRange && isEnable? mCurMonthTextPaint : mOtherMonthTextPaint);
}
}
CalendarColors.java代码如下(可自定义颜色):
public static final int middleColor = Color.rgb(255, 221, 217);
public static final int selectedColor = Color.rgb(255, 83, 67);
public static final int middleTxtColor = Color.rgb(64, 64, 64);
public static final int selectedTxtColor = Color.rgb(255, 255, 255);
在github地址:https://github.com/huanghaibin-dev/CalendarView,下载好项目,运行起来,接下来按照步骤修改
(1)CalendarUtil.java的differ(Calendar calendar1, Calendar calendar2)方法修改成如下:
/**
* 运算 大日期减小日期
* test Pass
*
* @param calendar1 calendar1 第二次点击的日期
* @param calendar2 calendar2 第一次点击的日期
* @return 大日期减小日期
*/
static int differ(Calendar calendar1, Calendar calendar2) {
if (calendar1 == null) {
return Integer.MIN_VALUE;
}
if (calendar2 == null) {
return Integer.MAX_VALUE;
}
java.util.Calendar date = java.util.Calendar.getInstance();
date.set(calendar1.getYear(), calendar1.getMonth() - 1, calendar1.getDay());//
long startTimeMills = date.getTimeInMillis();//获得起始时间戳
date.set(calendar2.getYear(), calendar2.getMonth() - 1, calendar2.getDay());//
long endTimeMills = date.getTimeInMillis();//获得结束时间戳
if (startTimeMills < endTimeMills) {
return (int) ((endTimeMills - startTimeMills) / ONE_DAY);
} else {
return (int) ((startTimeMills - endTimeMills) / ONE_DAY);
}
}
(2)RangeMonthView.java的onClick(View v)方法,修改某行代码即可,改成如下:(RangeWeekView.java同理)
//大概在168行,因为前面有改过某些代码,不知道准确行数了,如下代码
int compare = calendar.compareTo(mDelegate.mSelectedStartRangeCalendar);
//改成如下代码:(原因:比较两次日期大小,当第二次点击日期小于第一次点击日期时,会返回负数,反之会放回1,相等时放回0)
int compare = calendar.compareTo(mDelegate.mSelectedStartRangeCalendar);
if (compare < 0) {
compare = 1;
}
(3)RangeMonthView.java的isCalendarSelected(Calendar calendar),修改成如下代码:(RangeWeekView.java同理)
/**
* 日历是否被选中
*
* @param calendar calendar
* @return 日历是否被选中
*/
protected boolean isCalendarSelected(Calendar calendar) {
if (mDelegate.mSelectedStartRangeCalendar == null) {
return false;
}
if (onCalendarIntercept(calendar)) {
return false;
}
if (mDelegate.mSelectedEndRangeCalendar == null) {
return calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) == 0;
}
/*
*原先的代码如下:
*(当calendar在大于等于“第一次点击日期”而且小于等于“第二次点击日期”时为true
*这个逻辑对于我们“第一次点击日期”小于“第二次点击日期”就会返回false了)
* return calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) >= 0 &&
* calendar.compareTo(mDelegate.mSelectedEndRangeCalendar) <= 0;
* */
return (calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) >= 0 &&calendar.compareTo(mDelegate.mSelectedEndRangeCalendar) <= 0) ||
(calendar.compareTo(mDelegate.mSelectedEndRangeCalendar) >= 0 && calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) <= 0);
}
前方bug注意:修改三步骤后,结束日期小于开始日期是绘制了,但是不知道你选中的时间范围是什么,解决这个bug,按如下代码修改即可:
1.CalendarViewDelegate.java的getSelectCalendarRange()方法,当结束日期小于开始日期时,置换startTimeMills、endTimeMills即可,改成如下:
/**
* 获得选中范围
*
* @return 选中范围
*/
final List getSelectCalendarRange() {
if (mSelectMode != SELECT_MODE_RANGE) {
return null;
}
List calendars = new ArrayList<>();
if (mSelectedStartRangeCalendar == null ||
mSelectedEndRangeCalendar == null) {
return calendars;
}
final long ONE_DAY = 1000 * 3600 * 24;
java.util.Calendar date = java.util.Calendar.getInstance();
date.set(mSelectedStartRangeCalendar.getYear(),
mSelectedStartRangeCalendar.getMonth() - 1,
mSelectedStartRangeCalendar.getDay());//
long startTimeMills = date.getTimeInMillis();//获得起始时间戳
date.set(mSelectedEndRangeCalendar.getYear(),
mSelectedEndRangeCalendar.getMonth() - 1,
mSelectedEndRangeCalendar.getDay());//
long endTimeMills = date.getTimeInMillis();
//新增的代码,开始日期大于结束日期,置换
if (startTimeMills > endTimeMills) {
Long interTimeMills;
interTimeMills = startTimeMills;
startTimeMills = endTimeMills;
endTimeMills = interTimeMills;
}
for (long start = startTimeMills; start <= endTimeMills; start += ONE_DAY) {
date.setTimeInMillis(start);
Calendar calendar = new Calendar();
calendar.setYear(date.get(java.util.Calendar.YEAR));
calendar.setMonth(date.get(java.util.Calendar.MONTH) + 1);
calendar.setDay(date.get(java.util.Calendar.DAY_OF_MONTH));
if (mCalendarInterceptListener != null &&
mCalendarInterceptListener.onCalendarIntercept(calendar)) {
continue;
}
LunarCalendar.setupLunarCalendar(calendar);
calendars.add(calendar);
}
addSchemesFromMap(calendars);
return calendars;
}
改好后,运行测试。现在问题来了,怎么加入到项目里呢?原先我们引入日历这样子:
implementation 'com.haibin:calendarview:3.6.5'
改好源码后,怎么引入到项目里面呢?我的做法是把calendarview一整个libar引入到项目中,步骤如下:
1.选中calendarview右键——Show in Explorer(就是找到calendarview的路径);
2.选中项目(自己的项目)右键——New——Module——Import Gradle Project——Next——选择calendarview路径——Next;
3.修改calendarview的gradle,
calendarview、minSdkVersion、targetSdkVersion、versionCode、versionName这些值改成跟项目的一样,
把compile改成implementation,androidTestCompile改成androidTestImplementation,testCompiletestImplementation,
注意appcompat-v7、recyclerview-v7、junit版本号跟项目一致
4.clean、rebuild一下,运行即可
我入的坑:使用的是“范围选择”,要求左右滑动日历(点击<>)时,要显示当前年月,不然用户不知道滑动到哪里去了:
只需继承CalendarView.OnMonthChangeListener的
@Override
public void onMonthChange(int year, int month) {
//显示年月
}
注意:记得添加calendarView.setOnMonthChangeListener(this);
8.设置今天之后的日期不可点且置灰色,重点OnCalendarInterceptListener(select_mode要设置成支持拦截)
//设置日期拦截事件
@Override
public boolean onCalendarIntercept(Calendar calendar) {
String str = calendar.getYear() + "-" + (calendar.getMonth() < 10 ? "0" + calendar.getMonth() : calendar.getMonth()) + "-" + (calendar.getDay() < 10 ? "0" + calendar.getDay() : calendar.getDay());
String now= binding.cvHomeDormitoryAttendanceCalendarView.getCurYear() + "-" + binding.cvHomeDormitoryAttendanceCalendarView.getCurMonth() + "-" + binding.cvHomeDormitoryAttendanceCalendarView.getCurDay();
return TimeUtils.NowCompare(now, str);//现在日期,点击日期进行对比,点击日期>现在日期为true即拦截
}
@Override
public void onCalendarInterceptClick(Calendar calendar, boolean isClick) {
//点击拦截的日期回调,isClick为true时表示拦截到设置条件的日期,可以做相应提示
}