Android 一个日历控件的实现代码
转载 2017-05-19 作者:Othershe 我要评论
先看几张动态的效果图吧!
项目地址:https://github.com/Othershe/CalendarView
这里主要记录一下在编写日历控件过程中一些主要的点:
一、主要功能
1、支持农历、节气、常用节假日
2、日期范围设置,默认支持的最大日期范围[1900.1~2049.12]
3、默认选中日期设置
4、单选、多选
5、跳转到指定日期
6、通过自定义属性定制日期外观,以及简单的日期item布局配置
二、基本结构
我们要实现的日历控件采用ViewPager作为主框架,CalendarView继承ViewPager,这样就天生拥有左右滑动和缓存的功能。目前我们设定日历左右滑动为月份切换的操作,每一个月份显示通过自定义ViewGroup实现,也就是我们的MonthView,月份中的日期是通过layout布局解析出的View,根据月份的不同每个MonthView可能包含6 x 7或5 x 7个日期View,由于给ViewPager绑定数据需要通过PagerAdapter,所以继承PagerAdapter我们扩展了一个CalendarPagerAdapter,来完成MonthView的相关初始化和日期数据的绑定。
三、计算每个MonthView需要填充的日期数据
从上边的截图可以看出,每个MonthView的日期数据应该由上个月的后0~6天、当前月的天数和下个月的前0~6天组成。首先计算出当前月有多少天,这个简单,以及根据年月算出当前月的第一天是星期几:
1
2
3
4
5
|
public static int getFirstWeekOfMonth( int year, int month) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month, 1 ); return calendar.get(Calendar.DAY_OF_WEEK) - 1 ; } |
返回0代表周日,1~6代表周一到周六,以上边的截图为例,可以知道2017年5月的第一天是周一:week = getFirstWeekOfMonth(2017, 5-1),
按照如下伪码则可计算出包含的上个月的日期:
1
2
3
|
for ( int i = 0 ; i < week; i++) { ld = 上个月天数 - week + 1 + i; } |
至于包含的下个月的日期和当前MonthView显示的行数有关,如果 当前月的天数+week可以被7整除则不需要包含下月日期,否则需要计算包含的下月日期,伪码如下:
1
2
3
|
for ( int i = 0 ; i < 7 * 显示的行数 - 当月天数 - week; i++) { nd = i + 1 ; } |
这样需要的日期数据就计算完了,详细的算法可参考源码。
四、 计算日历的总页数
总页数应由日历的起始年月得到,其实就是确定ViewPager的总页数,这样好理解点。可按照如下方法计算:
其中dateStart、dateEnd是包含日历开始年月和结束年月的数组。这个count也是CalendarPagerAdapter必须的。
五、用position计算日期
PagerAdapter有个instantiateItem()方法:
1
2
3
|
public Object instantiateItem(ViewGroup container, int position) { return instantiateItem((View) container, position); } |
来创建ViewPager的每一页,所以日历每一页也是在这里创建的,也就是MonthView,这里有个关键的点就是根据 positon 参数推算出日历每一页对应的年月,然后通过年月计算出当前MonthView需要的日期数据。如何根据position推算出年月呢?
1
2
3
4
5
6
7
8
9
10
11
|
public static int [] positionToDate( int position, int startY, int startM) { int year = position / 12 + startY; int month = position % 12 + startM; if (month > 12 ) { month = month % 12 ; year = year + 1 ; } return new int []{year, month}; } |
其中startY、startM代表日历的其实年月。有了对应的年月就可以用第二点中的方式计算日期数据,然后填充到MothView中。
六、MothView
前边已经提到了,MonthView继承ViewGroup,也就是日历的每一页,接收到日期数据后,在MonthView中根据数据构造对应的日期View,然后添加View到MonthView中,最后通过onMeasure、onLayout确定每个View最终大小和位置。到这里运行一个ViewPager的基本条件就满足了,在上边提到的instantiateItem()方法中完成MothView的初始化:
1
2
3
4
5
6
7
8
9
|
public Object instantiateItem(ViewGroup container, int position) { MonthView view = new MonthView(container.getContext()); //根据position计算对应年、月 int [] date = CalendarUtil.positionToDate(position, dateStart[ 0 ], dateStart[ 1 ]); view.setDateList(CalendarUtil.getMonthDate(date[ 0 ], date[ 1 ]), SolarUtil.getMonthDays(date[ 0 ], date[ 1 ])); container.addView(view); return view; } |
这里只保留了核心的代码,当日历切换月份时,会自动根据position计算出对应月份的日期数据,然后传给MonthView,最后将MonthView添加到ViewPager中。
七、切换月份选中日期
按照目前的设定,当选择当前月的某天后,然后切换月份,新的月份中会找到上次选中的日期,并标记为选中状态,如果找不到则选中新月份的最后一天。其实逻辑很简单,关键是如何在新月份中找到相应的日期并选中。首先记录上次选中的日期,由于ViewPager默认会缓存两页,再加上当前页共三页,在CalendarPagerAdapter中根据position保存三页缓存,当ViewPager切换到某一页后会执行如下回调:
1
2
3
4
5
|
addOnPageChangeListener( new SimpleOnPageChangeListener() { @Override public void onPageSelected( int position) { } }); |
在onPageSelected(int position)方法中通过position从缓存中拿到对应的MonthView,也是是切换到的页,这样就能在MonthView中根据记录的日期找到对应的子日期View,然后更改为选中状态。
八、多选
一个理想的多选功能应该是在当前月份选中多个日期后,切换到其它月份,之后回到有选中日期的月份依然能够标记出选中的日期,因为ViewPager有默认的三页缓存,所以在当前月份切换到上月或下月不会有什么问题,但如果切换到前几个月或后几个月,再回到有选中日期的月份,由于之前缓存的页面已经被销毁重建,所以选中的月份也就看不到了。我们的日期点击事件在MonthView中,当每次点击选中时我们需要记录对应年月选中的日期,取消选中时要从记录中删除对应日期,怎么保存呢?在CalendarView类中我们定义一个SparseArray
其中的HashSet就是指定年月选中的日期,按照我们的规则设定不同年月转换得到的position是唯一对应的,所以我们用position作为SparseArray的key,最后在CalendarView中接收选中或取消选中的操作:
1
2
3
4
5
6
7
8
9
10
11
12
|
public void setLastChooseDate( int day, boolean flag) { HashSet<Integer> days = chooseDate.get(currentPosition); if (flag) { if (days == null ) { days = new HashSet<>(); chooseDate.put(currentPosition, days); } days.add(day); } else { days.remove(day); } } |
之后就是在月份切换过程中,根据保存的日期数据刷新对应的MonthView,实现选中状态的恢复,这个和第六点类似。
九、跳转到指定日期
要跳转到指定日期,首先要根据日期的年月计算出目标MonthView在日历中的position:
1
2
3
|
public static int dateToPosition( int year, int month, int startY, int startM) { return (year - startY) * 12 + month - startM; } |
ViewPager有一个setCurrentItem(int item, boolean smoothScroll)方法,这样就能跳转到position对应的MonthView,然后结合第六点的方法选中对应的日期View。这样跳转到日历设定日期范围内的任意一天都是没问题的。
十、自定义日历样式
目前CalendarView提供的自定义属性如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
< declare-styleable name = "CalendarView" > <!--是否多选--> < attr name = "multi_choose" format = "boolean" /> <!--是否显示农历--> < attr name = "show_lunar" format = "boolean" /> <!--是否显示上月和下月--> < attr name = "show_last_next" format = "boolean" /> <!--是否显示节假日--> < attr name = "show_holiday" format = "boolean" /> <!--是否显示节气--> < attr name = "show_term" format = "boolean" /> <!--开始日期(1990.1)--> < attr name = "date_start" format = "string" /> <!--结束日期(2020.12)--> < attr name = "date_end" format = "string" /> <!--默认展示、选中的日期(2016.10.1)--> < attr name = "date_init" format = "string" /> <!--是否禁用默认选中日期前的所有日期--> < attr name = "disable_before" format = "boolean" /> <!--阳历的日期颜色--> < attr name = "color_solar" format = "color" /> <!--阳历的日期尺寸--> < attr name = "size_solar" format = "integer" /> <!--农历的日期颜色--> < attr name = "color_lunar" format = "color" /> <!--农历的日期尺寸--> < attr name = "size_lunar" format = "integer" /> <!--节日文字颜色--> < attr name = "color_holiday" format = "color" /> <!--选中的日期文字颜色--> < attr name = "color_choose" format = "color" /> <!--选中的日期背景(图片)--> < attr name = "day_bg" format = "reference" /> <!--单选时切换月份,是否选中上次的日期--> < attr name = "switch_choose" format = "boolean" /> </ declare-styleable > |
基本可以满足日常的需求,默认的日期布局是阳历、阴历垂直排列,节假日会覆盖在农历上显示,这个从上边的静态截图可以看出。如果要使用其它的排列方式,例如水平排列等,就需要提供一个自定的layout(但目前只支持两个TextView显示)。例如:
1
2
3
4
5
6
7
8
|
calendarView.setOnCalendarViewAdapter(R.layout.item_layout, new CalendarViewAdapter() { @Override public TextView[] convertView(View view, DateBean date) { TextView solarDay = (TextView) view.findViewById(R.id.solar_day); TextView lunarDay = (TextView) view.findViewById(R.id.lunar_day); return new TextView[]{solarDay, lunarDay}; } }); |
给CalendarView绑定一个接口,传入lauoyt,然后返回一个代表阳历和农历的TextView数组。
十一、WeekView
我们将日期和星期的显示功能分割开了,所以CalendarView并不负责星期的显示,WeekView是星期显示的自定义View,从周日开始依次是周一到周六,可通过自定义属性来配置星期的显示文字,以及文字的颜色、尺寸,这个还是相对简单,具体可见Github中的使用介绍。
十二、小结
这里我们只介绍了日历的基本实现原理,和一些关键的点,其实这种实现方式相对还是比较简单的,容易理解,当然难免有不足的地方,后边根据需要再逐步完善和扩展吧。尽管Github上有许多现成的Calendar,但自己动手实现一个还是收获满满,一个看起来简单的东西,只有亲自尝试了才能体会到其中的滋味,最后希望对大家有所帮助吧!
您可能感兴趣的文章:
原文链接:http://www.jianshu.com/p/304c8e70d0bd?utm_source=tuicool&utm_medium=referral#
Android 一个日历控件的实现代码的更多相关文章
- Android自定义日历控件(继承系统控件实现)
Android自定义日历控件(继承系统控件实现) 主要步骤 编写布局 继承LinearLayout设置子控件 设置数据 继承TextView实现有圆圈背景的TextView 添加Attribute 添 ...
- 撸一个Android高性能日历控件,高仿魅族
Android原生的CalendarView根本无法满足我们日常开发的需要,在开发吾记APP的过程中,我觉得需要来一款高性能且美观简洁的日历控件,觉得魅族的日历风格十分适合,于是打算撸一款. gith ...
- android 自定义日历控件
日历控件View: /** * 日历控件 功能:获得点选的日期区间 * */ public class CalendarView extends View implements View.OnTouc ...
- Android图表日历控件组件
1.图表引擎 - AChartEngine AChartEngine是一款基于Android的图表绘制引擎,它为Android开发人员提供了非常多有用的图表绘制工具类,假设你须要在Android应用中 ...
- Android自定义View(CustomCalendar-定制日历控件)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeas ...
- IOS自定义日历控件的简单实现(附思想及过程)
因为程序要求要插入一个日历控件,该空间的要求是从当天开始及以后的六个月内的日历,上网查资料基本上都说只要获取两个条件(当月第一天周几和本月一共有多少天)就可以实现一个简单的日历,剩下的靠自己的简单逻辑 ...
- My97日历控件常用功能记录
My97相信大家都不陌生,应该是我所见过的最强大的一个日历控件了,最近的项目中也比较多地用到了此控件,而且项目中经常会有不同时间范围的需求,在此列出一些比较常用的日期范围格式的设置,尽管在My97的官 ...
- 转 My97日历控件常用功能记录
My97相信大家都不陌生,应该是我所见过的最强大的一个日历控件了,最近的项目中也比较多地用到了此控件,而且项目中经常会有不同时间范围的需求,在此列出一些比较常用的日期范围格式的设置,尽管在My97的官 ...
- JS日历控件集合----附效果图、源代码
http://www.cnblogs.com/yank/archive/2008/08/14/1267746.html 在进行开发的过程中,经常需要输入时间,特别是在进行查询.统计的时候,时间限定更为 ...
随机推荐
- 【转载】使用sklearn优雅地进行数据挖掘
原文:http://www.cnblogs.com/jasonfreak/p/5448462.html 目录 1 使用sklearn进行数据挖掘 1.1 数据挖掘的步骤 1.2 数据初貌 1.3 关键 ...
- cartographer 点云同步处理
1.点云同步处理的类 RangeDataCollator class RangeDataCollator { public: explicit RangeDataCollator( const st ...
- POJ 2398 Toy Storage(叉积+二分)
Description Mom and dad have a problem: their child, Reza, never puts his toys away when he is finis ...
- docker创建Redis集群
开始工作: yum install wegt ##安装下载工具 yum install net-tools ##安装网络工具 yum install tree ##安装tree命令(方便查看集群配置文 ...
- 解析电子墨水屏技术(工作原理与LCD的区别)【转】
转自:https://blog.csdn.net/weixin_42509369/article/details/84646808 阅读电子书早已成为大家生活中一部分,方便轻巧的电子版书籍更便于携带, ...
- eclipse快捷键 (包括查找类、方法、变量)
♦[Ct rl+T] 搜索当前接口的实现类 1. [ALT +/] 智能提示 此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不全方法和属性名称犯愁,当记不全类.方法和属性的名字时 ...
- 读书笔记-JavaScript高级程序设计(1)
1.组合继承 (JavaScript 中最常用的继承模式 ) (position: page168) (书中定义了两个变量名 SuperType SubType 乍一看 感觉不太能区分,我将改为 ...
- 代码中三种特殊注释——TODO、FIXME、XXX
在eclipse中,TODO.FIXME和XXX都会被eclipse的task视图所收集.在项目发布前,检查一下task视图是一个很好的习惯.进入window→show view→Other→输入ta ...
- vue.js过滤器
import Vue from 'vue' import { ENV } from '@/config/conf' const dateFormat = (str) => { var date ...
- linux安装selenium+chrome+phantomjs
1. 安装 selenium pip3 install selenium pip3 安装参考 2. 安装 ChromeDriver yum install chromedriver.x86_64 3. ...