描述
參考支付寶所制作的日期選擇頁,效果如下:
知識點
1、獲取指定月份有多少天
public static int getDayCountOfMonth(int year, int month) {
int[] arr = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int days = 0;
//如果是閏年,二月= 29天
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
arr[1] = 29;
}
try {
days = arr[month - 1];
} catch (Exception e) {
e.printStackTrace();
}
return days;
}
2、計算指定日期是周幾
/**
* 計算指定日期是周幾
* 1為周日,2為周一,3為周二,4為周三,5為周四,6為周五,7為周六
*/
public static int getDayOfWeekInMonth(int year, int month, int day) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DATE, day);
return calendar.get(Calendar.DAY_OF_WEEK);
}
3、基礎(chǔ)架構(gòu)
整個項目的結(jié)構(gòu)是RecyclerView嵌套RecyclerView:
紅色為外層RecyclerView,藍(lán)色為內(nèi)部RecyclerView。
外層RecyclerView包含了固定頭部條目與月份RecyclerView。
每個月份RecyclerView對應(yīng)了一個月份的日期展示。
4、數(shù)據(jù)存儲
關(guān)于日期數(shù)據(jù),我們需要考慮到很多情況。
因為日期數(shù)據(jù)可能很大,幾年的數(shù)據(jù)甚至更多,如果傳遞來傳遞去會在內(nèi)存中產(chǎn)生多份數(shù)據(jù)(根據(jù)引用地址不同可以看出)。
為了節(jié)省內(nèi)存,我選擇使用單例模式來實現(xiàn),并且是懶漢式單例模式,因為此功能用戶不一定每次打開App都會使用。
這樣做的好處不僅是節(jié)省了內(nèi)存、減少了來回傳遞數(shù)據(jù)的繁瑣操作,還有就是很方便地讓我們實現(xiàn)了回顯功能,從上面的Gif圖中也可以看出回顯功能。
那么還有一個問題就是,什么時候銷毀數(shù)據(jù)?
我的想法是,在用戶第一次打開日期選擇頁時,進(jìn)行數(shù)據(jù)的初始化,接著用戶反復(fù)在結(jié)果展示頁和日期選擇頁來回切換時,用的都是這一批數(shù)據(jù)。
當(dāng)用戶離開結(jié)果展示頁(提交、或者放棄)時,就要銷毀數(shù)據(jù),清理內(nèi)存,具體創(chuàng)建、銷毀邏輯可以查看代碼。
5、刷新
關(guān)于每個日期的狀態(tài)初始化以及選擇后的刷新,是比較麻煩的。
尤其是跨月、跨年選擇日期!因為我們的每個月是獨立的嵌套RecyclerView,當(dāng)你跨月選擇時,你就需要刷新多條內(nèi)部RecyclerView。
為了解決這種情況,我在代碼中使用了外部Adapter來進(jìn)行條目的刷新,并且不會調(diào)用全局刷新:adapter.notifyDataSetChanged();
選擇使用局部刷新:
adapter.notifyItemRangeChanged(int position, int count);
adapter.notifyItemChanged(int position);
不會去刷新無關(guān)的條目。
還有一點要說的是,嵌套RecyclerView的高度是不固定的:可能有5行、6行,也可能有7行。
關(guān)于嵌套RecyclerView的高度問題,確實已經(jīng)無需計算高度了,可以直接將內(nèi)部RecyclerView的高度設(shè)置為wrap_content
。
之前需要動態(tài)計算高度,是因為RecyclerView兼容包的Bug,不過已經(jīng)在23.2.0之后修復(fù)了,具體參考這里。
不過我在嵌套RecyclerView還是計算了高度,我嘗試了不計算高度,感覺性能遠(yuǎn)不如計算高度,具體就需要看看源碼了。
至此,難點基本已經(jīng)介紹完了。
不過項目當(dāng)中因為上述高度不固定的原因,產(chǎn)生了一個小問題:
在調(diào)用adapter.notifyItemChanged(int position)
時,當(dāng)position為0或1時,有時會導(dǎo)致屏幕自動滾動至頂部,目前還沒有找到合適的解決方案,各位看官可以自己試一試,找找解決方案。
感謝
其中RecyclerView的頭部固定引用了sticky-headers-recyclerview庫。
在Android日期顯示和選擇庫學(xué)到了很多東西。