日歷控件CalendarListView解析與重新實現
項目地址: CalendarListview 。分析的版本:063952b
功能介紹
CalendarListview 是一個日歷控件,效果如圖
總體設計
CalendarListview 日歷控件本質上是一個基于 RecyclerView 的列表控件。
DayPickerView 類直接繼承 RecyclerView ,是控件的主類,表示日歷。 SimpleMonthView 類繼承 View ,是列表的單元控件,表示單個月份。
總體的類結構圖為
詳細設計
SimpleMonthView
SimpleMonthView 類的核心方法 onDraw 將月份的繪制劃分為三個互相獨立的部分,見下圖。
每一部分的繪制都涉及兩個問題 位置選擇 與 文本內容 。除此之外,還要處理該控件的點擊事件。
繪制位置
關于繪制位置的選擇問題,在圖中都用顏色塊標出了,此外應該注意兩個問題。
1.第三部分第一行中每月的第一天不一定是周日,所有要留出一定的偏移量,偏移量等于當月第一天與周日的差。采用 findDayOffset() 方法計算出該偏移量。
2.要求出每月的天數,才能準確結尾。在工具類 CalendarUtils 中解決,重點是處理閏年二月的天數。
此外對整體控件的尺寸要設置其高度與寬度。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows + MONTH_HEADER_SIZE); } protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidth = w; }
文本內容
關于文本的內容涉及一些類。
1.標題繪制采用 DateUtils 工具類。
String DateUtils.formatDateRange(getContext(), millis, millis, flags);
簡單的說,該類第二三兩個參數表示起始和結束機器時間,第四個參數設置所顯示的人類時間格式。
2.星期繪制采用 DateFormatSymbols 。
//獲取星期標簽,如“周日”,“周一”等 String[] getShortWeekdays();
點擊事件
在觸摸回調方法中,首先使用 getDayFromLocation 方法根據點擊區域的得到某日( CalendarDay )。
public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { SimpleMonthAdapter.CalendarDay calendarDay = getDayFromLocation(event.getX(), event.getY()); if (calendarDay != null) { onDayClick(calendarDay); } } return true; }
而后設計接口 OnDayClickListener 將點擊事件委托出去。
public static abstract interface OnDayClickListener { public abstract void onDayClick(SimpleMonthView simpleMonthView, SimpleMonthAdapter.CalendarDay calendarDay); }
SimpleMonthAdapter
SimpleMonthAdapter 是處理日期數據集的數據適配器,繼承于 RecyclerView.Adapter 。首先要認識兩個內部類。
-
CalendarDay 類表示單獨的某一天,有三個參數表示年/月/日。
public static class CalendarDay { int day; int month; int year; }
-
SelectedDays<K> 表示一個時間段的首尾兩天,用來處理用戶的點擊行為。
public static class SelectedDays<CalendarDay> { private CalendarDay first; private CalendarDay last; }
1.首先在 getItemCount() 方法中設置要顯示的月份數目。
2.處理日期相關邏輯都放在 onBindViewHolder 方法中進行,完成數據和視圖的綁定。在這個方法中要告訴視圖 SimpleMonthView 該顯示哪年哪月,使用下列方法為視圖傳遞具體月份,并重繪。
@Override public void onBindViewHolder(ViewHolder viewHolder, int position){ SimpleMonthView v = viewHolder.simpleMonthView; HashMap<String, Integer> drawingParams = new HashMap<String, Integer>(); //傳遞年月參數給子視圖 v.setMonthParams(drawingParams); v.invalidate(); }
3. SimpleMonthAdapter 類實現了子視圖 SimpleMonthView 的 OnDayClickListener 接口,處理某日被選中后的事件。并給用戶留下一個處理白點。
public void onDayClick(SimpleMonthView simpleMonthView, CalendarDay calendarDay) { mController.onDayOfMonthSelected(calendarDay.year, calendarDay.month, calendarDay.day); setSelectedDay(calendarDay); }
其中選中日期方法的處理流程如下
DayPickerView
在主類中,要完成數據適配器的設置之外,還要處理屬性和用戶接口。
-
TypedArray 在構造適配器時將該參數傳遞過去。
new SimpleMonthAdapter(getContext(), mController, typedArray);
-
DatePickerController 接口雖然為用戶能直接接觸和感知,但該接口僅僅是控件點擊事件中留給用戶的處理白點,不再多說。
一些工具類
DateFormatSymbols
java.textDate.FormatSymbols 類作用是封裝一個局部時間,例如當月,年,星期。常見的 DateFormat 和 SimpleDateFormat 格式化類都基于該類。但不宜直接使用該類。
首先實例化 DateFormatSymbols 類,同時使用 Locale 類來本地化。
DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.ENGLISH);
而后獲取各類時間日期字符串
String[] ampm = dfs.getAmPmStrings(); String[] eras = dfs.getEras(); String[] months = dfs.getMonths(); String[] shortMonths = dfs.getShortMonths(); String[] weekdays = dfs.getWeekdays(); String[] shortWeekdays = dfs.getShortWeekdays();
DateFormat與SimpleDateFormat
專門負責日期格式,實現 Date 和 String 之間的轉化。
DateUtils
android.text.format.DateUtils 類是一個日期相關工具,依賴 創建時間和日期跨度 有關的文本。基本使用方法如下
DateUtils.formatDateRange(Context context, long startMillis,long endMillis, int flags);
最關鍵的參數是第四個,可以控制時間與日期格式,形式為 FORMAT_* ,例如
FORMAT_SHOW_TIME //顯示完整時間 FORMAT_SHOW_DATE //顯示完整日期 FORMAT_SHOW_WEEKDAY //顯示星期 FORMAT_SHOW_YEAR //顯示年 FORMAT_NO_MONTH_DAY //不顯示星期 FORMAT_NO_YEAR //不顯示年
重新實現
首先看下初步效果
1.獲取當月一共有多少天
LocalDate localDate = new LocalDate(); LocalDate firstDay = localDate.dayOfMonth().withMinimumValue(); LocalDate lastDay = localDate.dayOfMonth().withMaximumValue(); Period period = new Period(firstDay, lastDay); int dayCount = period.toStandardDays().getDays()+1;//注意使用toStandardDays()方法
2.獲取當月第一天對上一個周末的偏移量
LocalDate lastWeekday = firstDay.minusWeeks(1).dayOfWeek().withMaximumValue(); Period p = new Period(lastWeekday, firstDay); int offset = p.getDays();