自定义日历控android,Android 一个日历控件的实现小记

304c8e70d0bd

先看几张动态的效果图吧!

304c8e70d0bd

304c8e70d0bd

304c8e70d0bd

这里主要记录一下在编写日历控件过程中一些主要的点:

一、主要功能

1、支持农历、节气、常用节假日

2、日期范围设置,默认支持的最大日期范围[1900.1~2049.12]

3、禁用日期范围设置

4、初始化选中单个或多个日期

5、单选、多选操作

6、跳转到指定日期

7、替换农历为指定文字

8、通过自定义属性定制日期外观,以及简单的日期item布局配置

9、......

二、基本结构

我们要实现的日历控件采用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天组成。首先计算出当前月有多少天,这个简单,以及根据年月算出当前月的第一天是星期几:

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),按照如下伪码则可计算出包含的上个月的日期:

for (int i = 0; i < week; i++) {

ld = 上个月天数 - week + 1 + i;

}

至于包含的下个月的日期和当前MonthView显示的行数有关,如果 当前月的天数+week可以被7整除则不需要包含下月日期,否则需要计算包含的下月日期,伪码如下:

for (int i = 0; i < 7 * 显示的行数 - 当月天数 - week; i++) {

nd = i + 1;

}

这样需要的日期数据就计算完了,详细的算法可参考源码。

四、 计算日历的总页数

总页数应由日历的起始年月得到,其实就是确定ViewPager的总页数,这样好理解点。可按照如下方法计算:

count = (dateEnd[0] - dateStart[0]) * 12 + dateEnd[1] - dateStart[1] + 1

其中dateStart、dateEnd是包含日历开始年月和结束年月的数组。这个count也是CalendarPagerAdapter必须的。

五、用position计算日期

PagerAdapter有个instantiateItem()方法:

public Object instantiateItem(ViewGroup container, int position) {

return instantiateItem((View) container, position);

}

来创建ViewPager的每一页,所以日历每一页也是在这里创建的,也就是MonthView,这里有个关键的点就是根据 positon 参数推算出日历每一页对应的年月,然后通过年月计算出当前MonthView需要的日期数据。如何根据position推算出年月呢?

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的初始化:

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切换到某一页后会执行如下回调:

addOnPageChangeListener(new SimpleOnPageChangeListener() {

@Override

public void onPageSelected(int position) {

}

});

在onPageSelected(int position)方法中通过position从缓存中拿到对应的MonthView,也是是切换到的页,这样就能在MonthView中根据记录的日期找到对应的子日期View,然后更改为选中状态。

八、多选

一个理想的多选功能应该是在当前月份选中多个日期后,切换到其它月份,之后回到有选中日期的月份依然能够标记出选中的日期,因为ViewPager有默认的三页缓存,所以在当前月份切换到上月或下月不会有什么问题,但如果切换到前几个月或后几个月,再回到有选中日期的月份,由于之前缓存的页面已经被销毁重建,所以选中的月份也就看不到了。我们的日期点击事件在MonthView中,当每次点击选中时我们需要记录对应年月选中的日期,取消选中时要从记录中删除对应日期,怎么保存呢?在CalendarView类中我们定义一个SparseArray

SparseArray> chooseDate = new SparseArray<>()

其中的HashSet就是指定年月选中的日期,按照我们的规则设定不同年月转换得到的position是唯一对应的,所以我们用position作为SparseArray的key,最后在CalendarView中接收选中或取消选中的操作:

public void setChooseDate(int day, boolean flag, int position) {

if (position == -1) {

position = currentPosition;

}

HashSet days = chooseDate.get(position);

if (flag) {

if (days == null) {

days = new HashSet<>();

chooseDate.put(position, days);

}

days.add(day);

positions.add(position);

} else {

days.remove(day);

}

}

之后就是在月份切换过程中,根据保存的日期数据刷新对应的MonthView,实现选中状态的恢复,这个和第六点类似。

九、跳转到指定日期

要跳转到指定日期,首先要根据日期的年月计算出目标MonthView在日历中的position:

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提供的自定义属性如下:

属性名

格式

描述

默认值

choose_type

enum

设置单选(single)、多选(multi)

single

show_lunar

boolean

是否显示农历

true

show_last_next

boolean

是否在MonthView显示上月和下月日期

true

show_holiday

boolean

是否显示节假日

true

show_term

boolean

是否显示节气

true

switch_choose

boolean

单选时切换月份,是否选中上次的日期

true

solar_color

color

阳历日期的颜色

solar_size

integer

阳历的日期尺寸

14

lunar_color

color

农历的日期颜色

lunar_size

integer

农历的日期尺寸

8

holiday_color

color

节假日、节气的颜色

choose_color

color

选中的日期颜色

day_bg

reference

选中的日期背景(图片)

CalendarView相关方法:

方法名

描述

setInitDate(String date)

设置日历的初始显示年月

setStartEndDate(String startDate, String endDate)

设置日历开始、结束年月

setDisableStartEndDate(String startDate, String endDate)

设置日历的禁用日期范围(小于startDate、大于endDate禁用)

setSpecifyMap(HashMap map)

将显示农历的区域替换成指定文字

setSingleDate(String date)

设置单选时初始选中的日期(不设置则不默认选中)

getSingleDate()

得到单选时选中的日期

setMultiDate(List dates)

设置多选时默认选中的日期集合

getMultiDate()

得到多选时选中的全部日期

toSpecifyDate(int year, int month, int day)

单选时跳转到指定年月日

setOnCalendarViewAdapter(int layoutId, CalendarViewAdapter adapter)

设置自定义日期item样式

init()

日期初始化(以上属性配置完后调用)

setOnPagerChangeListener(OnPagerChangeListener listener)

设置月份切换回调

setOnSingleChooseListener(OnSingleChooseListener listener)

设置单选回调

setOnMultiChooseListener(OnMultiChooseListener listener)

设置多选回调

today()

单选时跳转到今天

nextMonth()

跳转到下个月

lastMonth()

跳转到上个月

nextYear()

跳转到下一年的当前月

lastYear()

跳转到上一年的当前月

toStart()

跳转到日历的开始年月

toEnd()

跳转到日历的结束年月

CalendarUtil.getCurrentDate()

获得当前日期(今天)

默认的日期布局是阳历、阴历垂直排列,节假日会覆盖在农历上显示,这个从上边的静态截图可以看出。如果要使用其它的排列方式,例如水平排列等,就需要提供一个自定的layout(但目前只支持两个TextView显示)。例如:

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.mzph.cn/news/529112.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python先返回再处理_python xpath解析返回对象怎么处理

3 4 5 text 6 7 ... 8 ... 9 ......10 11 12 ...13 ...14 ......15 16 17 18

android文件读取工具类,Android 下读取Assets Properties操作封装工具类

Android 下读取Assets Properties操作封装工具类发布时间&#xff1a;2018-06-03作者&#xff1a;laosun阅读(2081)为了方便使用&#xff0c;首先创建BaseApplication类&#xff0c;如下所示&#xff1a;import android.app.Application;import android.content.Context;/*** C…

python粘性拓展_如何将tkinter小部件置于粘性框架中

在google中使用“如何使tkinter网格扩展”&#xff0c;我遇到了这个问题。 引用布莱恩奥克利的话Rows and columns have "weight" which describes how they grow or shrink to fill extra space >in the master. By default a row or column has a weight of zer…

android 固件 编辑器,RK3288编译 Android 5.1 固件

1 准备工作编译 Android 对机器的配置要求较高&#xff1a;64 位 CPU16GB 物理内存交换内存30GB 空闲的磁盘空间用于构建&#xff0c;源码树另外占用大约 25GBUbuntu 14.04 操作系统八核i7&#xff0c;编译完成需要一个半小时安装 JDK 7:sudo apt-get install openjdk-7-jdkUbu…

python解压到指定文件夹_在Python中压缩和解压文件

Python部落(python.freelycode.com)组织翻译&#xff0c;禁止转载&#xff0c;欢迎转发。 如果你已经使用计算机一段时间&#xff0c;你可能遇到了.zip扩展名的文件。它们是可以保存许多其他文件&#xff0c;文件夹和子文件夹的压缩内容的特殊文件。这种类型的文件在使用互联网…

android bar布局,Android学习路线(十)如何将Action Bar叠放在你的布局上

默认状况下&#xff0c;action bar出如今activity窗口的顶部&#xff0c;略微减小了activity布局的总空间。若是你想隐藏或者显示action bar&#xff0c;在这堂用户体验的课程中&#xff0c;你能够经过调用htmlFigure 1. Gallerys action bar in overlay mode.android为了不在a…

geant4运行例子_Geant4--一次编译,运行多个Run,极大提升模拟效率

文|梁佐佐应唐光毅博士/后之约&#xff0c;对于Geant4模拟&#xff0c;我们看是否能解决这么一个问题&#xff1a;我现在想模拟探测器不同角度下的响应&#xff0c;每次模拟需要/run/beamOn 100&#xff0c; 可是我真的不想一遍一遍的去http://DetectorConstruction.cc中修改几…

python3.7基础教程_关于本教程 |《Python 官方文档:入门教程 3.7.0》| Python 技术论坛...

本文档最新版为 3.8&#xff0c;旧版本可能放弃维护&#xff0c;推荐阅读最新版&#xff01; Python 入门教程 Python 是一门简单易学且功能强大的编程语言。它拥有高效的高级数据结构&#xff0c;并能够用简单又有效的方式进行面向对象编程。Python 优雅的语法和动态类型&…

android listview countdowntimer,Android-ListView中的CountDownTimer随机闪烁

我正在使用计时器制作列表视图&#xff0c;每个计时器都有不同的截止日期&#xff0c;具体取决于数据库(类似于拍卖)Time now new Time();now.setToNow();now.normalize(true);nowMillis now.toMillis(true);..String endtime a.get(position).get(TAG_ENDTIME);Integer tim…

echart实现3d地图_3D飞线效果——让线“飞”起来的秘密

在城市规划、统计、交通等行业&#xff0c;地图可视化已成为最直接也最吸引眼球的一种表达方式。例如人群迁徙、人口流动、OD出行、职住分析、客流来源等众多场景都需要用到飞线效果呈现。2D飞线效果图随着可视化技术的进一步发展&#xff0c;传统的2D飞线效果略显单调&#xf…

ad域管理与维护_在NAS SMB卷上使用VisualSVN Server维护代码库

VisualSVN Server[1] 是 Windows 平台上流行的 SVN 形式的代码管理工具。以下我们将介绍把 NAS SMB 卷作为 VisualSVN 代码库存储中心时会遇到的几个问题以及相应的解决方法。1. 安装错误的解决方法我们以 VisualSVN Server 3.3.1 版本为例&#xff0c;在安装 VisualSVN Server…

android 开发art,Android应用开发之Android 系统启动原理(art 虚拟机)

本文将带你了解Android应用开发之Android 系统启动原理(art 虚拟机)&#xff0c;希望本文对大家学Android有所帮助。Android 系统启动原理(art 虚拟机)一、虚拟机的启动Android 是一个 Linux 的虚拟机&#xff0c;当虚拟机启动的时候&#xff0c;会执行手机根目录下的 init.r…

电脑文件夹可以分屏的软件_电脑上什么便签软件可以添加音频?

提及便签&#xff0c;很多人都会很自然地想到手机便签。这是因为随着智能手机和移动互联网的发展&#xff0c;现在很多手机上都有了系统自带的便签app。其实&#xff0c;除了手机便签外&#xff0c;还有电脑便签呢&#xff01;这不&#xff0c;Win7及其以上版本的电脑上还有系统…

jsp form提交到后台中文乱码_JSP与servlet之间的数据传递

【51】Jsp与Servlet之间的传值有两种&#xff0c;一种是Jsp传值给Sevlet&#xff0c;另一种是Servlet传值给Jsp&#xff1b;使用request、response对象完成传值&#xff0c;具体实现如下&#xff1a;Jsp与Servlet之间的传值有两种&#xff0c;一种是Jsp传值给Sevlet&#xff0c…

android jni 中jnienv,android JNI中JNIEnv類型和jobject類型的解釋

JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv *env, jobject obj){cout<}對於這個方法參數中的JNIEnv* env參數的解釋:JNIEnv類型實際上代表了Java環境&#xff0c;通過這個JNIEnv* 指針&#xff0c;就可以對Java端的代碼進行操作。例如&#xff0c;…

yang模型中rpc_领域驱动模型(DDD)设计讲解

一. 什么是领域驱动模型(DDD)&#xff1f;领域驱动模型一种设计思想&#xff0c;我们又称为DDD设计思想。是一种为了解决传统设计思想带来的维护困难&#xff0c;沟通困难和交互困难而产生的一种新的思想。也解决了在部分公司中&#xff0c;一个项目组就是一套服务&#xff0c;…

鸿蒙系统操作界面跟苹果很像,鸿蒙手机UI界面曝出!图标拟物化、操作逻辑近似苹果iOS13...

原标题&#xff1a;鸿蒙手机UI界面曝出&#xff01;图标拟物化、操作逻辑近似苹果iOS13​【IT爆料王-原创文章-具备版权效力】就在近日&#xff0c;笔者收到了网友的匿名私信&#xff0c;提供给笔者华为鸿蒙系统的UI界面截图&#xff0c;以及搭载鸿蒙系统的华为手机的曝光图片。…

python3中的int类型占64位,有没有什么办法来强制Python来使用64位整数的Windows?

I’ve noticed that whenever any integer surpasses 2^31-1 my number heavy code suffers a large slowdown, despite the fact I’m using a 64 bit build of Python on a 64bit version of Windows. This seems to be true on Python 2.7 and Python 3. I’ve read that Wi…

crtsiii型无砟轨道板_无砟轨道裂缝破损怎么修补

随着高速铁路、客运专线、城市地铁的快速发展&#xff0c;无砟轨道轨道板&#xff08;道床板&#xff09;广泛应用&#xff0c;但施工中和运营期都发现轨道板混凝土存在不同程度的微细裂缝&#xff0c;对无砟轨道造成了一定的病害。高铁轨道板裂缝是不可避免的。为确保无砟轨道…

c调用python第三方库_Python使用ctypes模块调用DLL函数之C语言数组与numpy数组传递...

在Python语言中&#xff0c;可以使用ctypes模块调用其它如C语言编写的动态链接库DLL文件中的函数&#xff0c;在提高软件运行效率的同时&#xff0c;也可以充分利用目前市面上各种第三方的DLL库函数&#xff0c;以扩充Python软件的功能及应用领域&#xff0c;减少重复编写代码、…