1. 前言
1.最近笔者在实习,需要做一个炫酷的自定义日历来在满足实际项目的需求,当让这里不能把实际需求放在这里,不然我就那啥了,你懂的。
2.所以自己又重新设计了需求(内容完全不一样了,但UI有些类似),写了一个新的、十分炫酷的个性化日历,来记录您最近两个月的学习情况(记录您预期学习计划,以及实际完成的情况)。
3.这篇博客我会尽可能的详细的去创作,所以这对于新手来说提升会很大,包括我自己,我也是刚刚学习Android不久,其实刚拿到需求的时候,我根本不知道该如何下手,不怕你们笑话,我当时连自定义View都不太会,业务逻辑编写能力也是弱的不行,但最后我还是完成了,所以我在这里会穿插的分享一下我学习的方法以及学习的思路,希望感兴趣的朋友耐心的阅读,同时也希望您能够喜欢。
PS:由于篇幅和知识重点分布的特点,我把它拆分成了三个章节,一点一滴记录它是如何完成的,本Demo的推荐阅读顺序是:
【 ① —> ② —> ③ 】或者【 ② —> ③】,本章编号为“③”
相关参考链接:
2. 最终的效果图(本章效果图)如下:
3. 本章简介
这一章节呢,我们主要是在【英式的自定义日历】基础之上进行编写的,所以一定要先看英式的自定义日历是怎么实现的,否者会看的云里雾里的,废话不多说,接下来就是手把手分享如何修改成一个**【中式的自定义日历】。
4. 【自定义日历(中式)】的具体实现
(1)具体实现思路
具体的实现思路就两个字:【对比】,寻找【英式日历】和【中式日历】的异同点,然后对症下药,屡试不爽,还有一个注意的点就是写完代码之后一定要测试一下,修改手机上的时间(主要改月份),不然的话,会存在你自认为的写的没有bug,其实某些特殊的月份会存在很严重的bug,你是不是对此充满了好奇呢?不着急,真相马上就会揭晓 O(∩_∩)O
(2)具体实现步骤
(1)修改星期标识
修改起来非常简单,因为我没有把星期标识包含在自定义日历中,所以只是在布局文件中修改下布局就行了。
PS:不过这里小小的抽取一下,不然每个星期标识都是一样的,就只是Text文本不一样,而且还要写七个,代码会变得繁琐,如下所示(未抽取):
<LinearLayout
... ... ... >
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="一"
android:textColor="@color/colorGreen"
android:textSize="16sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="二"
android:textColor="@color/colorGreen"
android:textSize="16sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="三"
android:textColor="@color/colorGreen"
android:textSize="16sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="四"
android:textColor="@color/colorGreen"
android:textSize="16sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="五"
android:textColor="#2BCEA3"
android:textSize="16sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="六"
android:textColor="@color/colorGreen"
android:textSize="16sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="日"
android:textColor="@color/colorGreen"
android:textSize="16sp" />
</LinearLayout>
可以在style文件中把相同的属性的都抽取出来:
<style name="weekLogo">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/colorGreen</item>
<item name="android:textSize">16sp</item>
</style>
抽取出来之后你看看,现在每个星期标识就只要写两行代码:
<LinearLayout
... ... ...>
<TextView
style="@style/weekLogo"
android:text="一" />
<TextView
style="@style/weekLogo"
android:text="二" />
<TextView
style="@style/weekLogo"
android:text="三" />
<TextView
style="@style/weekLogo"
android:text="四" />
<TextView
style="@style/weekLogo"
android:text="五" />
<TextView
style="@style/weekLogo"
android:text="六" />
<TextView
style="@style/weekLogo"
android:text="日" />
</LinearLayout>
(2)在DayController里面修改日期对象的位置
有了上面的分析,就好修改了:只要处理两种情况就好了:
if (dayOfWeek != 1) {//【星期1 ~ 星期6】
//行号
mDayModel.locationY = weekOfMonth - 1;
//列号
mDayModel.locationX = dayOfWeek - 2;
}
else {//特殊情况【星期天】
//行号
mDayModel.locationY = weekOfMonth - 2;//原来
//列号
mDayModel.locationX = dayOfWeek + 5;
}
(3)在CalendarView里面修改日期对象的点击事件
1.首先修改的无效点击事件
有了上面的分析很简单,不管是月初还是月末的无效点击事件,都只向左移动了一个位置,所以相比【英式】的只要都减一就好了,如下图:
2.在修改点击之后显示的日期
这个比上一个又更简单,因为它设置的是日期,不是位置,所以只要清楚周一到周六都是行号不变,之前不是减一嘛,现在设置回去,就要加一,对于特殊情况【星期天】来说呢,相对与周一到周六:行号加一就行了,列号不变,因为我是设置日期。
if (locationX != 6) {//【星期1 ~ 星期6】
calendar.set(Calendar.WEEK_OF_MONTH, locationY + 1);//相比【英式】:不变
calendar.set(Calendar.DAY_OF_WEEK, (locationX + 2));//相比【英式】:加一
}
else { //特殊情况【星期天】
calendar.set(Calendar.WEEK_OF_MONTH, locationY + 2);//相比【星期1 ~ 星期6】:加一
calendar.set(Calendar.DAY_OF_WEEK, (locationX + 2));//相比【星期1 ~ 星期6】:不变
}
3.写到这里,效果图如下:(日期的显示和点击事件都正确)
(4)测试是否存在潜在的BUG
从刚刚的效果图来看,确实没有任何的问题,但你要知道一年有12个月,这里只有一两个月,不能断定写的代码就没有任何的问题。
这时你需要更换系统的时间,测试一下其它的月份是否有误,你至少测一年以上(12次)以上,把所有的月份给测到。
测试的结果有【两种】特殊的情况会出现BUG:
【1】: 当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来!例如:(2017年1月xx号)
【2】: 当一个月的最后一号(仅当一个月有六周的时候),并且【30号/31号】是星期天,会多一行空白!例如:(2017年4月xx号)和(2016年7月xx号)
(2017年4月xx号):
(2016年7月xx号):
(5)修改潜在的BUG
(1)首先改行数:
分析:
首先我们来分析一下怎么改,改哪里,最好修改的就是【行数】了,第一种情况是【少一行】,第二种情况是【多一行】,感受到了吗?第一种情况只要行数加一,第二种情况只要减一就行了。
① 在DayController类中添加的一个标识变量,在把原来计算日期对象高度的那行代码重写,行数就解决了
/**
* 设置一个标记位置
* 1:代表第一种特殊情况; 2:代表第二种特殊情况;
*/
static int flag = 0;
/**
* 处理特殊情况
*
* 【1】: 当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来!
* 例如:(2017年1月xx号)
* 【2】: 当一个月的最后一号(仅当一个月有六周的时候),并且【30号/31号】是星期天,会多一行空白!
* 例如:(2017年4月xx号)或者(2016年7月xx号)
*/
int total_week_of_month = calendar.getActualMaximum(Calendar.WEEK_OF_MONTH);
int dayModelHeight = height / total_week_of_month;
//由于是静态的变量,存在记忆,复位一下,不然一定有问题
if (flag != 0) {
flag = 0;
}
//设置为当月一号,处理第一种情况【1】: 当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来!
calendar.set(Calendar.DAY_OF_MONTH, 1);
if ((calendar.get(Calendar.DAY_OF_WEEK)) == 1) {
dayModelHeight = height / (total_week_of_month + 1);
flag = 1;
}
//设置为当月最后一号,处理第二种情况【2】:当一个月的最后一号(仅当一个月有六周的时候),并且【30号/31号】是星期天,会多一行空白!
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
if (total_week_of_month == 6 && (calendar.get(Calendar.DAY_OF_WEEK)) == 1) {
dayModelHeight = height / (total_week_of_month - 1);
flag = 2;
}
② 在CalendarView类中,修改两种特殊情况下的LocationY(日期对象的行号):
/**
* 处理特殊情况
*
* 【1】: 当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来!
* 例如:(2017年1月xx号)
* 【2】: 当一个月的最后一号(仅当一个月有六周的时候),并且【30号/31号】是星期天,会多一行空白!
* 例如:(2017年4月xx号)和(2016年7月xx号)
*/
if (DayController.getFlag() != 0) {
//【1】: 当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来!
if (DayController.getFlag() == 1) {
locationY = (int) ((calendar.getActualMaximum(Calendar.WEEK_OF_MONTH) + 1) * y / getMeasuredHeight());
}
//【2】: 当一个月的最后一号(仅当一个月有六周的时候),并且【30号/31号】是星期天,会多一行空白!
else if (DayController.getFlag() == 2) {
locationY = (int) ((calendar.getActualMaximum(Calendar.WEEK_OF_MONTH) - 1) * y / getMeasuredHeight());
}
}
(2)修改行数之后看看有什么效果:
第一种情况:当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来! 例如:(2017年1月xx号):
结果是:【行数正确】,【日期对象显示依然有误】,【点击事件错乱】(点击空白地方竟然从2017年一月跑到二月去了),如下图所示(2017年1月xx号):
第二种情况:当一个月的最后一号(仅当一个月有六周的时候),并且【30号/31号】是星期天,会多一行空白!例如:(2017年4月xx号)和(2016年7月xx号)
结果是:你会神奇的发现,【行数正确】,【日期对象显示正确】,【点击事件也正确】,这种情况已经解决了,如下图所示(2017年4月xx号):
(3)继续修改第一种特殊情况(第二种特殊情况已解决):
① 在DayController类中【设置对象位置方法中】单独为第一种情况加一个判断,其实也很简单,由于只需要将日历整个向下移动一个距离,所以【列号不变】,【行号加一】就可以了。
/**
* 设置日期对象位置
*/
public static void setLocation(int weekOfMonth,int dayOfWeek, DayModel mDayModel) {
//【1】: 当一个月的【1号】是星期天的时候,会少一行,并且【1号】显示不出来!
if (flag == 1) {
//一般情况(星期1~6)
if (dayOfWeek != 1) {
//行号
mDayModel.locationY = weekOfMonth;
//列号
mDayModel.locationX = dayOfWeek - 2;
}
//特殊情况(星期天)
else {
//行号
mDayModel.locationY = weekOfMonth - 1;
//列号
mDayModel.locationX = dayOfWeek + 5;
}
} else {
//... ...
}
② 在Calendar类中:
先修改无效点击事件,再【屏蔽没有日期处的点击事件方法】中单独为第一种情况加一个判断,第一行:很好判断,就一号一个对象可以点击,其它都屏蔽;最后一行,只是日历整体向下移动了一个距离,所以【locationX不变】,【locationY加一】就行了。
/**
*屏蔽没有日期处的点击事件
*/
private boolean invalidClick(int locationY,int locationX) {
//单独处理第一种特殊情况
if (DayController.getFlag() == 1) {
//选中的日历第一行的时候,前面有几个空格点击的时候肯定不能做相应的操作
if (locationY == 0) {
if (locationX < 6) {//location值为0 1 2 3 4 5的都不能点击
Toast.makeText(context, "无效点击", Toast.LENGTH_SHORT).show();
return false;
}
}
//选中的日历第最后一行的时候,后面有几个空格点击的时候肯定也是不能做相应的操作
else if (locationY == calendar.getActualMaximum(Calendar.WEEK_OF_MONTH)) {//相比原来我添加了一行,所以行数加一
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
if (locationX > calendar.get(Calendar.DAY_OF_WEEK) - 2) {
Toast.makeText(context, "无效点击", Toast.LENGTH_SHORT).show();
return false;
}
}
}else{
//... ...
}
return true;
}
再再【设置选中日期的方法】中单独,为第一种情况加一个判断,原理也很简单,由于整体向下移动一个距离,所以【locationX不变】,【locationY 加一】就可以了。
/**
* 设置选中的日期
*/
private void setSelectedDay(int locationY,int locationX) {
//单独处理第一种特殊情况
if (DayController.flag == 1) {
if (locationX != 6) { //【星期天1~6】
calendar.set(Calendar.WEEK_OF_MONTH, (int) locationY);//相比正常行号加一
calendar.set(Calendar.DAY_OF_WEEK, (int) (locationX + 2));
}
else { //特殊情况【星期天】
calendar.set(Calendar.WEEK_OF_MONTH, (int) locationY + 1);//相比正常行号加一
calendar.set(Calendar.DAY_OF_WEEK, (int) (locationX + 2));
}
}else{
//... ...
}
}
(4)修改完成之后再看看第一种特殊情况解决了没有:
答案是:解决了~,如下图所示:
到此为止【自定义日历(中式)】就从【自定义日历(英式)】中修改过来了。
5. 本章小结:
这是篇博文详细的记录了【自定义日历(中式)】是如何从【自定义日历(英式)】中修改过来的,之前也说了我会尽可能的详细的去创作,由于篇幅和知识重点分布的特点,我把它拆分成了三个章节,一点一滴记录它是如何完成的,中间会扩展一些东西,包括一些常用到的android开发技术,和我自己开发过程中遇到的问题,我都会提到,因为这样创作的话对于新手来说提升会很大,因为我自己也是新手,刚刚学习Android不久,没有太多的经验,所以大神勿喷噢O(∩_∩)O
PS:本Demo的推荐阅读顺序是【 ① —> ② —> ③ 】或者【 ② —> ③】,本章编号为“③”