Android-签到日历

原创 2017年09月09日 21:39:51

前言


项目中签到的日历尝试过用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,在下一次创建时复用,如此将空间复杂度降到最低,关键代码如下:
        ...
        @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;
    }
OK啦!

扩展


既然这种形式的签到日历都实现了,那么顺便删减修改下代码实现另外一种形式的签到日历,里面的逻辑和算法和上面的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







版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

RecyclerView刷新加载库-SRecyclerView

最近在使用前段时间"业余时间"项目封装的RecyclerView时,发现了好几个问题,比如:刷新头部和加载尾部耦合性太高,要是以后想换个刷新头部或加载尾部,必然改动很大,不利于扩展,刷新手势处理和加载...

好吧,这又是一个圆形进度条!

项目需要(好吧,又是项目需要),需要一个圆形渐变进度条,不想用网上的,因为找了下,没找到合适的,就自己写了一个,完事后完善了一下细节。老规矩,先上图 关键代码如下: mPa...

Android 自定义日历-实现签到订约功能

前言aiyang:公司项目需要做一个签到送积分的系统,要求app实现这种签到功能。先在网上找了一些资料,有些用自定义View画图实现,其实对于普通码农对绘图会没耐心看各自算法。所以自己动手撸一个通用型...

Android中带签到功能的日历(积分)

利用gridview实现签到的日历
  • dailog
  • dailog
  • 2016年08月28日 10:05
  • 3494

android签到日历实现

先看最终实现的效果 事情是这样的,那一天我仍一如既往的在写代码(看新闻),隔了10米远我就看到一个傻。哦 不对 看见我们的经理走了过来。我的内心是这样的。 “怎么样,BUG改的怎么样了?”...

android 可签到的自定义日历控件

首先是MainActivity,源码如下public class MainActivity extends Activity { private SignCalendar calendar;...
  • Wheidan
  • Wheidan
  • 2016年11月23日 16:32
  • 1150

Android实现签到日历控件

  • 2017年10月23日 15:33
  • 44.13MB
  • 下载

new Date() 签到日历系统

var myData=new Date(); console.log(myData);  //返回日期     Tue Dec 20 2016 10:27:36 GMT+0800 (CST) cons...

Android 数据库自定义日历签到

  • 2017年06月13日 15:02
  • 573KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android-签到日历
举报原因:
原因补充:

(最多只允许输入30个字)