還不瞭解Calendar?實現自定義Android日曆,看這篇就夠了
背景介紹
日曆對我的生活而言是一個容易被忽視,而又十分重要的東西。在Android中,我們也常常需要操作日曆去實現一些需求。比如根據日期獲取對應資料,或者承載了一些需求的自定義日曆。為了方便對日期的操作,誕生了Calendar 類。這大大簡化了我們的計算。
事實上,我們只需要知道如何操作Calendar就行了。本篇我們將一起來了解下Calendar,並且實現一個自定義日曆。
方便的Calendar類
Calendar是幹什麼的?
The Calendar class is an abstract class that provides methods for converting between a specific instant in time and a set of calendar fields such as YEAR, MONTH, DAY_OF_MONTH, HOUR, and so on, and for manipulating the calendar fields, such as getting the date of the next week.
上面是Android文件中對Calendar是什麼的簡單介紹。大概的意思就是說,Calendar是一個抽象類。它提供了一些用於一個具體時間和Calendar的欄位(比如,YEAR、MONTH、DAY_OF_MONTH、HOUR等)互相轉換的方法,以及對Calendar的欄位的操作方法(比如,獲取下一週的日期)。
綜上所述,Calendar就是一個操作日曆的工具類。
Calendar的使用
獲得一個Calendar例項
Calendar calendar = Calendar.getInstance(); //這個方法獲取到的是預設的Calendar例項。
//一般使用預設的就好,它會根據app執行的時區、語言環境自動建立相應的Calendar例項。
Calendar calendar = Calendar.getInstance(Locale.CHINA); //根據Locale來獲取相應的Calendar例項。
Calendar calendar = Calendar.getInstance(TimeZone.getDefaultRef(), Locale.CHINA);
//根據TimeZone和Locale來獲取相應的Calendar例項。
//這些方法最終呼叫的都是createCalendar()
private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
return new GregorianCalendar(zone, aLocale); //Android中使用了GregorianCalendar。
//注意這裡每次都是new一個新的Calendar例項。
}
挑幾個方法特別說明下
set(f, value)
- 呼叫該方法可以把Calendar的某個欄位f的值設定為指定值value。
- 這裡需要注意:DAY_OF_MONTH欄位是從1開始,如果設定0,表示上個月的最後一天;MONTH欄位是從0開始,如果設定12,表示下一年的第一個月。
- set()方法設定後,僅僅是改變了Calendar的指定欄位的值,但是Calendar表示的日期並沒有重新計算。Calendar將會在下次呼叫get()、getTime()、getTimeInMillis()、add()或roll()方法時,真實的重新計算日期。
add(f, delta)
效果等同於呼叫了set(f, get(f) + delta)l。roll(f, delta)
效果和add()有些類似。但是它的作用範圍限制在f欄位上,不會影響到其它欄位。舉個栗子。
Calendar calendar = Calendar.getInstance();
calendar.set(2016,1,1);
calendar.roll(Calendar.DAY_OF_MONTH, 32);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dateFormat.format(calendar.getTime()));
輸出:
2016-01-01
看出來沒?它只在DAY_OF_MONTH這個欄位裡迴圈,而不會導致其它欄位發生變化。
getFirstDayOfWeek()
這個方法用於獲取一個周的第一天是周幾。一般一個周的第一天是星期天,但法國的是星期一。clone()
這個方法是由Clonable介面提供的。可以克隆一個Calendar例項。這很有用,可以避免混亂的操作Calendar。
其它方法的使用請點選連結看
幾個特殊的規則
- GregorianCalendar的時間原點是1970-01-01 00:00:00.000。所以通過getTimeInMillis()獲得的時間戳就是時間原點的偏移量。
- 日期大小規則
Thus, 23:59 on Dec 31, 1999 < 00:00 on Jan 1, 2000 < 00:01 on Jan 1, 2000.
12:00 am (midnight) < 12:01 am, and 12:00 pm (noon) < 12:01 pm.
擼個自定義日曆
上個效果圖,比較簡陋,各位看官見諒啊。主要是為了說明下Calendar的使用。
主要使用了ViewPager + Fragment + RecyclerView完成。但這不是重點。重點是Calendar!所以我就放核心的程式碼。其它部分和大家平時使用的ViewPager + Fragment + RecyclerView也都差不多。且看~
protected void initData() {
dateList.clear();
initialDate = (Date) getArguments().getSerializable(KEY_DATE); //獲取傳過來的Date,用於確定初始的calendar
Calendar calendar = Calendar.getInstance(Locale.CHINA); //獲取China區Calendar例項,實際是GregorianCalendar的一個例項
calendar.setTime(initialDate); //初始化日期
int maxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); //獲得當前日期所在月份有多少天(或者說day的最大值),用於後面的計算
Calendar calendarClone = (Calendar) calendar.clone(); //克隆一個Calendar再進行操作,避免造成混亂
calendarClone.set(Calendar.DAY_OF_MONTH, 1); //將日期調到當前月份的第一天
int startDayOfWeek = calendarClone.get(Calendar.DAY_OF_WEEK); //獲得當前日期所在月份的第一天是星期幾
calendarClone.set(Calendar.DAY_OF_MONTH, maxDay); //將日期調到當前月份的最後一天
int endDayOfWeek = calendarClone.get(Calendar.DAY_OF_WEEK); //獲得當前日期所在月份的最後一天是星期幾
/**
* 計算上一個月在本月日曆頁出現的那幾天.
* 比如,startDayOfWeek = 3,表示當月第一天是星期二,所以日曆向前會空出2天的位置,那麼讓上月的最後兩天顯示在星期日和星期一的位置上.
*/
int startEmptyCount = startDayOfWeek - 1; //上月在本月日曆頁因該出現的天數。
Calendar preCalendar = (Calendar) calendar.clone(); //克隆一份再操作
preCalendar.set(Calendar.DAY_OF_MONTH, 1); //將日期調到當月第一天
preCalendar.add(Calendar.DAY_OF_MONTH, -startEmptyCount); //向前推移startEmptyCount天
for (int i = 0; i < startEmptyCount; i++) {
DateInfo dateInfo = new DateInfo(); //使用DateInfo來儲存所需的相關資訊
dateInfo.setDate(preCalendar.getTime());
dateInfo.setType(DateInfo.PRE_MONTH); //標記日期資訊的型別為上個月
dateList.add(dateInfo); //將日期新增到陣列中
preCalendar.add(Calendar.DAY_OF_MONTH, 1); //向後推移一天
}
/**
* 計算當月的每一天日期
*/
calendar.set(Calendar.DAY_OF_MONTH, 1); //由於是獲取當月日期資訊,所以直接操作當月Calendar即可。將日期調為當月第一天
for (int i = 0; i < maxDay; i++) {
DateInfo dateInfo = new DateInfo();
dateInfo.setDate(calendar.getTime());
dateInfo.setType(DateInfo.CURRENT_MONTH); //標記日期資訊的型別為當月
dateList.add(dateInfo);
calendar.add(Calendar.DAY_OF_MONTH, 1); //向後推移一天
}
/**
* 計算下月在本月日曆頁出現的那幾天。
* 比如,endDayOfWeek = 6,表示當月第二天是星期五,所以日曆向後會空出1天的位置,那麼讓下月的第一天顯示在星期六的位置上。
*/
int endEmptyCount = 7 - endDayOfWeek; //下月在本月日曆頁上因該出現的天數
Calendar afterCalendar = (Calendar) calendar.clone(); //同樣,克隆一份在操作
for (int i = 0; i < endEmptyCount; i++) {
DateInfo dateInfo = new DateInfo();
dateInfo.setDate(afterCalendar.getTime());
dateInfo.setType(DateInfo.AFTER_MONTH); //將DateInfo型別標記為下個月
dateList.add(dateInfo);
afterCalendar.add(Calendar.DAY_OF_MONTH, 1); //向後推移一天
}
}
//DateInfo有必要放一下。不過這僅代表我的思路。
private static class DateInfo {
private static final int PRE_MONTH = 1;
private static final int CURRENT_MONTH = PRE_MONTH + 1;
private static final int AFTER_MONTH = CURRENT_MONTH + 1;
private static final int WEEK_TITLE = AFTER_MONTH + 1;
private Date date;
private int type;
private String weekTitle;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getWeekTitle() {
return weekTitle;
}
public void setWeekTitle(String weekTitle) {
this.weekTitle = weekTitle;
}
}
後面只需要把dateList 中的資料放到RecyclerView中就可以。
總結
到此想必大家也對Calendar有了個基本點的瞭解。平時用的比較多的就是get(),getTime(),set(),add(),roll()等方法,我在上面提了幾個特殊點的。其它的可以到上面提到那個連結裡熟悉,寫的比較詳細的。這波【農夫山泉】我就不搬了。