Android從0到1仿去哪兒選擇日期區(qū)間控件
本篇學(xué)習(xí)的內(nèi)容
第一,recyclerview的多類型item的使用;
第二,calendar的基本使用與日期計算;
第三,DialogFragment的基本使用。
需求
最近公司項目需要一個選擇時間區(qū)間的控件,效果跟去哪兒網(wǎng)選擇住宿時間區(qū)間非常像,先來看看最終放入我項目中的效果圖(壓縮后圖片比較不清晰,請見諒,最終在移動端顯示效果比這個更佳)如下圖:
選擇開始后

選擇開始與結(jié)束后

開始與結(jié)束為同一天

實現(xiàn)思路
第一步,日歷主體實現(xiàn)
首先,利用recycerview的多item布局實現(xiàn)日歷主體部分,其中,有兩種item類型;
第一種,月份;
這個簡單,不做過多說明
第二種,日期。
有GridLayoutManager實現(xiàn),設(shè)置SpanCount為7,注意空白數(shù)據(jù)的填充和每個item的樣式類型。
既然數(shù)據(jù)itme有兩種類型,那么數(shù)據(jù)源也是會有兩種類型的,要顯示的數(shù)據(jù)體為了方便咱們可以用一個object類型,就可以匹配itme不同的數(shù)據(jù)類型了
第二步,就是利用calendar計算出這個recyclerview的數(shù)據(jù)源
1.先看今天所在月份的第一天為星期幾;
2.填充空白日期;
3.然后循環(huán)12次,也就是12個月份;
4.每次月份循環(huán)時,看看當前月的天數(shù),然后循環(huán)天數(shù),添加數(shù)據(jù);
5.每次天數(shù)循環(huán)后,利用calendar增加1;(最后,注意添加節(jié)日)
另外一點
item中的日期布局,我想講一下,每個item

底色是兩半邊,因為選中時開始與結(jié)束的itme只需要顯示半邊顏色。
這個布局畫一條中心線,然后從中間分開,利用在左在右的布局形式分別設(shè)置兩個view。節(jié)日在選中情況下,會變成開始字樣,這時注意變色。
實現(xiàn)過程
利用DialogFragment實現(xiàn)從下往上彈出框效果
在DialogFragment的onCreateView方法中初始化view布局,并且設(shè)置相應(yīng)的參數(shù)動畫,代碼注釋已經(jīng)很清晰。
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {// 去掉默認titlethis.getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);// 外部點擊是否可以收起該dialoggetDialog().setCanceledOnTouchOutside(false);Window window = this.getDialog().getWindow();if (window != null) {//去掉dialog默認的paddingwindow.getDecorView().setPadding(0, 0, 0, 0);WindowManager.LayoutParams lp = window.getAttributes();lp.width = WindowManager.LayoutParams.MATCH_PARENT;// 設(shè)置高度為屏幕高度的四分之三lp.height = UIUtils.getScreenHeight(getActivity()) * 3 / 4;//設(shè)置dialog的位置在底部lp.gravity = Gravity.BOTTOM;//設(shè)置dialog的動畫lp.windowAnimations = R.style.AnimBottom;window.setAttributes(lp);window.setBackgroundDrawable(new ColorDrawable());}View view = inflater.inflate(R.layout.dialog_choose_date, container, false);initView(view);initRecyclerView();initListener();initData();return view;}
初始化recyclerview
final GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 7);layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {@Overridepublic int getSpanSize(int position) {// 這個item占有幾個位置return (mChooseRecyclerAdapter.getItemViewType(position)== ChooseDateRecyclerAdapter.TYPE_YEAR_MONTH ? layoutManager.getSpanCount() : 1);}});mDateRecyclerView.setAdapter(mChooseRecyclerAdapter);mDateRecyclerView.setLayoutManager(layoutManager);mDateRecyclerView.setItemAnimator(new DefaultItemAnimator());
如上所示,GridLayoutManager可以設(shè)置一行劃分為幾個區(qū)域來顯示幾個item,然后在setSpanSizeLookup方法中設(shè)置當前item類型可以占據(jù)幾個區(qū)域以此來調(diào)節(jié)item的顯示寬度。
如果一行中顯示區(qū)域不足以顯示該item的長度,則會另起一行。所以這個控件用起來是相當靈活而且爽歪歪。
初始化適配器數(shù)據(jù)
這里其實是整個實現(xiàn)過程中的一個難點,咱們按照上面的思路來實現(xiàn)數(shù)據(jù)的初始化。
設(shè)置當當前月份的第一天。
Calendar calendar = Calendar.getInstance();calendar.set(Calendar.DAY_OF_MONTH, 1);// 添加月份數(shù)據(jù)mData.add(new ChooseDateBean(1, String.valueOf(calendar.get(Calendar.MONTH) + 1) + "月"));
然后查看第一天是星期幾,設(shè)置相應(yīng)的空白數(shù)據(jù)。
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);for (int j = 1; j < dayOfWeek; j++) {DateOfDayBean bean = new DateOfDayBean();bean.setDayOfMonth("");mData.add(new ChooseDateBean(2, bean));}
獲取當前月份的天數(shù)
private int getDayCountByYearAndMonth(int year, int month) {int days = 30;switch (month) {case 1:case 3:case 5:case 7:case 8:case 10:case 12:days = 31;break;case 4:case 6:case 9:case 11:days = 30;break;case 2:if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {days = 29;} else {days = 28;}break;}return days;}
循環(huán)遍歷當前月份天數(shù),添加相應(yīng)的數(shù)據(jù)(因為代碼最終會全部上傳,所以這里只寫重要部分來講解)
DateOfDayBean bean = new DateOfDayBean();bean.setDayOfMonth(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));bean.setCalendar(calendar);
這里我遇到了一個坑,就是計算今天的時候,用了:
calendar.compareTo(Calendar.getInstance()));這個方法,這個方法其實比較算了時分秒,完全相同的時間戳才會相等,后面該用判斷年月日相同就認為是同一天了。
點擊邏輯部分
這個部分也是難點之一,點擊分為三個部分來進行判斷顯示:
選了開始,沒選結(jié)束
選了開始與結(jié)束
什么都沒有選
其中選了開始與結(jié)束,還有一種樣式就是開始與結(jié)束為同一天,這種需要區(qū)分對待。詳細見代碼。
源碼地址:
https://github.com/zangp/ChooseDateView
