CalendarListView解析與從新實現

有圖地址java

項目地址:CalendarListview。分析的版本:063952bandroid

功能介紹

CalendarListview是一個日曆控件,效果如圖git

圖片描述

整體設計

CalendarListview日曆控件本質上是一個基於RecyclerView的列表控件。
DayPickerView類直接繼承RecyclerView,是控件的主類,表示日曆。SimpleMonthView類繼承View,是列表的單元控件,表示單個月份。github

整體的類結構圖爲
zongtisheji.pngide

詳細設計

SimpleMonthView

SimpleMonthView類的核心方法onDraw將月份的繪製劃分爲三個互相獨立的部分,見下圖。工具

size.png
每一部分的繪製都涉及兩個問題位置選擇文本內容。除此以外,還要處理該控件的點擊事件。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

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類實現了子視圖SimpleMonthViewOnDayClickListener接口,處理某日被選中後的事件。並給用戶留下一個處理白點。

public void onDayClick(SimpleMonthView simpleMonthView, CalendarDay calendarDay) {
    mController.onDayOfMonthSelected(calendarDay.year, calendarDay.month, calendarDay.day);
    setSelectedDay(calendarDay);    
}

其中選中日期方法的處理流程以下

setSelectedDay.png

DayPickerView

在主類中,要完成數據適配器的設置以外,還要處理屬性和用戶接口。

  • TypedArray 在構造適配器時將該參數傳遞過去。

new SimpleMonthAdapter(getContext(), mController, typedArray);
  • DatePickerController接口雖然爲用戶能直接接觸和感知,但該接口僅僅是控件點擊事件中留給用戶的處理白點,再也不多說。

一些工具類

DateFormatSymbols

java.textDate.FormatSymbols類做用是封裝一個局部時間,例如當月,年,星期。常見的DateFormatSimpleDateFormat格式化類都基於該類。但不宜直接使用該類。

首先實例化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

專門負責日期格式,實現DateString之間的轉化。

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        //不顯示年

從新實現

首先看下初步效果

demo.jpg

原控件使用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();

參考文獻

相關文章
相關標籤/搜索