自定义日历控android,Android 一个日历控件的实现小记
先看幾張動態的效果圖吧!
這里主要記錄一下在編寫日歷控件過程中一些主要的點:
一、主要功能
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,但自己動手實現一個還是收獲滿滿,一個看起來簡單的東西,只有親自嘗試了才能體會到其中的滋味,最后希望對大家有所幫助吧!
總結
以上是生活随笔為你收集整理的自定义日历控android,Android 一个日历控件的实现小记的全部內容,希望文章能夠幫你解決所遇到的問題。

- 上一篇: Linux在文件第一行添加字符,使用sh
- 下一篇: mysql 全局不重复_php uniq