有圖地址java
項目地址:CalendarListview。分析的版本:063952bandroid
CalendarListview
是一個日曆控件,效果如圖git
CalendarListview
日曆控件本質上是一個基於RecyclerView
的列表控件。DayPickerView
類直接繼承RecyclerView
,是控件的主類,表示日曆。SimpleMonthView
類繼承View
,是列表的單元控件,表示單個月份。github
整體的類結構圖爲ide
SimpleMonthView
類的核心方法onDraw
將月份的繪製劃分爲三個互相獨立的部分,見下圖。工具
每一部分的繪製都涉及兩個問題位置選擇與文本內容。除此以外,還要處理該控件的點擊事件。spa
關於繪製位置的選擇問題,在圖中都用顏色塊標出了,此外應該注意兩個問題。
1.第三部分第一行中每個月的第一天不必定是週日,全部要留出必定的偏移量,偏移量等於當月第一天與週日的差。採用findDayOffset()
方法計算出該偏移量。
2.要求出每個月的天數,才能準確結尾。在工具類CalendarUtils
中解決,重點是處理閏年二月的天數。設計
此外對總體控件的尺寸要設置其高度與寬度。code
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; }
關於文本的內容涉及一些類。orm
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
是處理日期數據集的數據適配器,繼承於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); }
其中選中日期方法的處理流程以下
在主類中,要完成數據適配器的設置以外,還要處理屬性和用戶接口。
TypedArray
在構造適配器時將該參數傳遞過去。
new SimpleMonthAdapter(getContext(), mController, typedArray);
DatePickerController
接口雖然爲用戶能直接接觸和感知,但該接口僅僅是控件點擊事件中留給用戶的處理白點,再也不多說。
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();
專門負責日期格式,實現Date
和String
之間的轉化。
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 //不顯示年
首先看下初步效果
原控件使用Calender
來處理日期,Calender
的弱點不少,在處理日期時也不簡潔。這裏改用使用Joda-time
實現日曆列表。
首先考慮幾個問題:
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();