赞
踩
项目中签到的日历尝试过用GridView或者Recyclerview来实现,用ViewFilper或者ViewPager实现切换动画,功能是实现了,但是第一次启动时,因为GridView或者Recyclerview要创建多个布局,导致界面卡顿,后来想到可以用自定义View的方式来实现,就是可能略微麻烦一些,不过还是尽量实现了一下,于是就有了下面的效果及这篇博客,算是对Calendar的用法总结。
日历展示部分用自定义View的方式实现,左右的切换用ViewPager来实现,根据当前ViewPager的位置(Position)来计算当前的年和月,并画出对应的日期,先来看看日历展示部分View的实现过程。日历总共有6行7列的展示界面,那也就是需要画6*7个日期,那么1号的位置就是1号所对应的周几的位置,假如1号是周五,那么1号对应的下标就为5,1号之前的日期为前一个月的日期,当月最大天数以后的日期为后一个月的日期,以下为关键性代码:
- ...
- //当月信息
- int year = 1970 + currentPosition / 12;
- int month = currentPosition % 12;
- calendar.set(year, month, 1);
- int firstDay = calendar.get(Calendar.DAY_OF_WEEK) - 1;
- int selectMonthMaxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
- //上一个月的最大天数
- calendar.add(Calendar.MONTH, -1);
- int previousMonthMaxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
- ...
首先根据ViewPager的当前位置计算需要展示的年月,并设置当前日期为展示年月的1号,获取了该月1号的下标和该月的最大天数后,获取上一个月最大的天数,用于展示上月数据。ok,接下来就是画日历了,这个部分很简单,关键代码如下:
- for (int i = 1; i <= 42; i++) {
- int copyI = i - 1;
- int x = (copyI % 7) * itemWidth + itemWidth / 2;
- int y = (copyI / 7) * itemHeight + itemHeight / 2 + ...;
- if (i <= firstDay) {//前一月数据
- ...
- int day = previousMonthMaxDay - firstDay + i;
- canvas.drawText(String.valueOf(day), x, y, paint);
- ...
- } else if (i > selectMonthMaxDay + firstDay) {//后一月数据
- ...
- int day = i - firstDay - selectMonthMaxDay;
- canvas.drawText(String.valueOf(day), x, y, paint);
- ...
- } else {//当前月数据
- int day = i - firstDay;
- ...
- canvas.drawText(String.valueOf(day), x, y, paint);
- ...
- }
- }
签到标志的绘制需要提供个日期和签到是否成功的标志,然后画日期的时候判断一下即可:
HashMap<String, Boolean> signRecords = new HashMap<>(); signRecords.put("2017-07-12", true); signRecords.put("2017-07-23", true); //画签到标志 date.set(year, month, day); String dateStr = format.format(date.getTime()); if (signRecords.containsKey(dateStr)) { ... if (signRecords.get(dateStr)) { canvas.drawBitmap(...); } else { canvas.drawBitmap(...); } }
OK,这样日历的展示部分就完成了。但实际项目中的需求可能是当用户点击选中某个日期的时候,查看当前的签到信息,那么这个点击位置怎么判断呢,其实也很简单,获取到当前点击位置的x,y值,判断所在的位置:
- private int getPosition(float x, float y) {
- y -= config.weekHeight;
- int y1 = (int) (y / itemHeight);
- int x1 = (int) (x / itemWidth);
- return y1 * 7 + x1;
- }
至于农历的实现就是用网上的公历转农历的算法,换算一下即可,但是需要注意的换算的算法比较复杂,如果我们每个日期都用这个算法换算一下的话,肯定时间复杂度不是很理想了,优化如下:
- //如果阳历是当在同一年,同一月,day是lastDay的后一天,并且
- //阴历lastLunarDay<29的时候,
- //此时的阴历直接在前天的基础上加1,否则重新计算
- // false为在前一天的基础上已经修改了,直接可以使用lunar实例
- if (lastYear == year && lastMonth == month && day - lastDay == 1) {
- if (lastLunarDay > 0 && lastLunarDay < 29) {
- //上个日期的基础上加1
- lunar.lunarDay = lastLunarDay + 1;
- ...
- }
- }
- ...
- //否则重新使用农历转换算法计算日期
这样实际的效果就是,启动和切换都更加流畅,哈哈,至此我们的签到日历的日历展示部分就算完成了,至于日历的切换就是根据当前位置计算日历日期,这里需要优化的地方是,ViewPager的切换如果每次都创建一个自定义View的话,很不好,我们可以把ViewPager中销毁的View,在下一次创建时复用,如此将空间复杂度降到最低,关键代码如下:
OK啦!
... @Override public Object instantiateItem(ViewGroup container, int position) { ZWCalendar calendarView = getContent(position); container.addView(calendarView); return calendarView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { destroyViews.add((ZWCalendar) object); container.removeView((View) object); } ... private ZWCalendar getContent(int position) { ZWCalendar calendarView; if (destroyViews.size() != 0) { calendarView = destroyViews.valueAt(0); destroyViews.remove(calendarView); } else { calendarView = new ZWCalendar(getContext()); ... viewSet.add(calendarView); } ... return calendarView; }
既然这种形式的签到日历都实现了,那么顺便删减修改下代码实现另外一种形式的签到日历,里面的逻辑和算法和上面的View大同小异,只不过没有日期的选择。先看看效果:
具体使用方法看代码吧,很简单。
为了方便使用,这里定义了一些属性,如下:代码中的使用:
<declare-styleable name="ZWCalendarView"> <attr name="weekHeight" />//周几的标题高度 <attr name="weekTextSize" /> <attr name="weekBackgroundColor" /> <attr name="weekTextColor" /> <attr name="calendarTextSize" />//日历的字体大小 <attr name="calendarTextColor" /> <attr name="isShowOtherMonth" format="boolean" />//是否显示上月和下月的日期 <attr name="otherMonthTextColor" format="color" /> <attr name="isShowLunar" />//是否显示农历 <attr name="lunarTextColor" />//农历字体的颜色,大小等 <attr name="lunarTextSize" /> <attr name="todayTextColor" /> <attr name="selectColor" format="color" />//当前选中的圆形颜色 <attr name="selectTextColor" format="color" />//选中的字体颜色 <attr name="signIconSuccessId" format="integer" />//签到成功的标志 <attr name="signIconErrorId" format="integer" /> <attr name="signIconSize" format="dimension" />//签到标志的大小 <attr name="signTextColor" />//签到字体的颜色 <attr name="limitFutureMonth" />//是否显示未来年月的日历 </declare-styleable>另外那个扩展的签到日历的使用和这个稍稍不同,具体去看代码吧。
calendarView.setSelectListener(new ZWCalendarView.SelectListener() { @Override public void change(int year, int month) { //当前切换的监听 } @Override public void select(int year, int month, int day, int week) { //当前选中的监听 } }); //代码选中一个日期 calendarView.selectDate(2017, 9, 3); //显示上个月 calendarView.showPreviousMonth(); //显示下个月 calendarView.showNextMonth(); //返回今天 calendarView.backToday();
额,签到日历虽然实现了,但是还没有用到项目中(你问我为什么不用? ,你是程序员你应该懂得),虽然经过了测试,但是可能还是会有一些潜在bug,有问题我再改吧。
代码:https://github.com/HzwSunshine/SignCalendarProgect
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。