diff options
| author | Fabrice Di Meglio <fdimeglio@google.com> | 2013-08-29 15:24:19 -0700 |
|---|---|---|
| committer | Fabrice Di Meglio <fdimeglio@google.com> | 2013-09-24 16:25:57 -0700 |
| commit | c6afe76dfe641afa32dab6e0b4740d780072d2c6 (patch) | |
| tree | 969bacc0d6355023e6cbcb61c192246fb14101e6 /core/java/android/widget/CalendarView.java | |
| parent | af568530203dc2ee59f5fda4895d28b29dd92226 (diff) | |
Decouple CalendarView code
- introduce a CalendarViewDelegate interface to be able to have several
CalendarView implementations
- code refactoring
Change-Id: Ib98fc51471c33a86ef58210e06176a4b4d253f01
Diffstat (limited to 'core/java/android/widget/CalendarView.java')
| -rw-r--r-- | core/java/android/widget/CalendarView.java | 2674 |
1 files changed, 1479 insertions, 1195 deletions
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index 3d45cacaee8b..a87c7d29e405 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -80,234 +80,7 @@ public class CalendarView extends FrameLayout { */ private static final String LOG_TAG = CalendarView.class.getSimpleName(); - /** - * Default value whether to show week number. - */ - private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; - - /** - * The number of milliseconds in a day.e - */ - private static final long MILLIS_IN_DAY = 86400000L; - - /** - * The number of day in a week. - */ - private static final int DAYS_PER_WEEK = 7; - - /** - * The number of milliseconds in a week. - */ - private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; - - /** - * Affects when the month selection will change while scrolling upe - */ - private static final int SCROLL_HYST_WEEKS = 2; - - /** - * How long the GoTo fling animation should last. - */ - private static final int GOTO_SCROLL_DURATION = 1000; - - /** - * The duration of the adjustment upon a user scroll in milliseconds. - */ - private static final int ADJUSTMENT_SCROLL_DURATION = 500; - - /** - * How long to wait after receiving an onScrollStateChanged notification - * before acting on it. - */ - private static final int SCROLL_CHANGE_DELAY = 40; - - /** - * String for parsing dates. - */ - private static final String DATE_FORMAT = "MM/dd/yyyy"; - - /** - * The default minimal date. - */ - private static final String DEFAULT_MIN_DATE = "01/01/1900"; - - /** - * The default maximal date. - */ - private static final String DEFAULT_MAX_DATE = "01/01/2100"; - - private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; - - private static final int DEFAULT_DATE_TEXT_SIZE = 14; - - private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; - - private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; - - private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; - - private static final int UNSCALED_BOTTOM_BUFFER = 20; - - private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; - - private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; - - private final int mWeekSeperatorLineWidth; - - private int mDateTextSize; - - private Drawable mSelectedDateVerticalBar; - - private final int mSelectedDateVerticalBarWidth; - - private int mSelectedWeekBackgroundColor; - - private int mFocusedMonthDateColor; - - private int mUnfocusedMonthDateColor; - - private int mWeekSeparatorLineColor; - - private int mWeekNumberColor; - - private int mWeekDayTextAppearanceResId; - - private int mDateTextAppearanceResId; - - /** - * The top offset of the weeks list. - */ - private int mListScrollTopOffset = 2; - - /** - * The visible height of a week view. - */ - private int mWeekMinVisibleHeight = 12; - - /** - * The visible height of a week view. - */ - private int mBottomBuffer = 20; - - /** - * The number of shown weeks. - */ - private int mShownWeekCount; - - /** - * Flag whether to show the week number. - */ - private boolean mShowWeekNumber; - - /** - * The number of day per week to be shown. - */ - private int mDaysPerWeek = 7; - - /** - * The friction of the week list while flinging. - */ - private float mFriction = .05f; - - /** - * Scale for adjusting velocity of the week list while flinging. - */ - private float mVelocityScale = 0.333f; - - /** - * The adapter for the weeks list. - */ - private WeeksAdapter mAdapter; - - /** - * The weeks list. - */ - private ListView mListView; - - /** - * The name of the month to display. - */ - private TextView mMonthName; - - /** - * The header with week day names. - */ - private ViewGroup mDayNamesHeader; - - /** - * Cached labels for the week names header. - */ - private String[] mDayLabels; - - /** - * The first day of the week. - */ - private int mFirstDayOfWeek; - - /** - * Which month should be displayed/highlighted [0-11]. - */ - private int mCurrentMonthDisplayed = -1; - - /** - * Used for tracking during a scroll. - */ - private long mPreviousScrollPosition; - - /** - * Used for tracking which direction the view is scrolling. - */ - private boolean mIsScrollingUp = false; - - /** - * The previous scroll state of the weeks ListView. - */ - private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * The current scroll state of the weeks ListView. - */ - private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * Listener for changes in the selected day. - */ - private OnDateChangeListener mOnDateChangeListener; - - /** - * Command for adjusting the position after a scroll/fling. - */ - private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); - - /** - * Temporary instance to avoid multiple instantiations. - */ - private Calendar mTempDate; - - /** - * The first day of the focused month. - */ - private Calendar mFirstDayOfMonth; - - /** - * The start date of the range supported by this picker. - */ - private Calendar mMinDate; - - /** - * The end date of the range supported by this picker. - */ - private Calendar mMaxDate; - - /** - * Date format for parsing dates. - */ - private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); - - /** - * The current locale. - */ - private Locale mCurrentLocale; + private CalendarViewDelegate mDelegate; /** * The callback used to indicate the user changes the date. @@ -340,85 +113,7 @@ public class CalendarView extends FrameLayout { public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - - final TypedArray attributesArray = context.obtainStyledAttributes( - attrs, R.styleable.CalendarView, defStyleAttr, defStyleRes); - mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, - DEFAULT_SHOW_WEEK_NUMBER); - mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); - String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); - if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { - parseDate(DEFAULT_MIN_DATE, mMinDate); - } - String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); - if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { - parseDate(DEFAULT_MAX_DATE, mMaxDate); - } - if (mMaxDate.before(mMinDate)) { - throw new IllegalArgumentException("Max date cannot be before min date."); - } - mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, - DEFAULT_SHOWN_WEEK_COUNT); - mSelectedWeekBackgroundColor = attributesArray.getColor( - R.styleable.CalendarView_selectedWeekBackgroundColor, 0); - mFocusedMonthDateColor = attributesArray.getColor( - R.styleable.CalendarView_focusedMonthDateColor, 0); - mUnfocusedMonthDateColor = attributesArray.getColor( - R.styleable.CalendarView_unfocusedMonthDateColor, 0); - mWeekSeparatorLineColor = attributesArray.getColor( - R.styleable.CalendarView_weekSeparatorLineColor, 0); - mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); - mSelectedDateVerticalBar = attributesArray.getDrawable( - R.styleable.CalendarView_selectedDateVerticalBar); - - mDateTextAppearanceResId = attributesArray.getResourceId( - R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); - updateDateTextSize(); - - mWeekDayTextAppearanceResId = attributesArray.getResourceId( - R.styleable.CalendarView_weekDayTextAppearance, - DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); - attributesArray.recycle(); - - DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); - mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); - mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); - mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_BOTTOM_BUFFER, displayMetrics); - mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); - mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); - - LayoutInflater layoutInflater = (LayoutInflater) context - .getSystemService(Service.LAYOUT_INFLATER_SERVICE); - View content = layoutInflater.inflate(R.layout.calendar_view, null, false); - addView(content); - - mListView = (ListView) findViewById(R.id.list); - mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); - mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); - - setUpHeader(); - setUpListView(); - setUpAdapter(); - - // go to today or whichever is close to today min or max date - mTempDate.setTimeInMillis(System.currentTimeMillis()); - if (mTempDate.before(mMinDate)) { - goTo(mMinDate, false, true, true); - } else if (mMaxDate.before(mTempDate)) { - goTo(mMaxDate, false, true, true); - } else { - goTo(mTempDate, false, true, true); - } - - invalidate(); + mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes); } /** @@ -429,10 +124,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_shownWeekCount */ public void setShownWeekCount(int count) { - if (mShownWeekCount != count) { - mShownWeekCount = count; - invalidate(); - } + mDelegate.setShownWeekCount(count); } /** @@ -443,7 +135,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_shownWeekCount */ public int getShownWeekCount() { - return mShownWeekCount; + return mDelegate.getShownWeekCount(); } /** @@ -454,16 +146,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor */ public void setSelectedWeekBackgroundColor(int color) { - if (mSelectedWeekBackgroundColor != color) { - mSelectedWeekBackgroundColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasSelectedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setSelectedWeekBackgroundColor(color); } /** @@ -474,7 +157,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor */ public int getSelectedWeekBackgroundColor() { - return mSelectedWeekBackgroundColor; + return mDelegate.getSelectedWeekBackgroundColor(); } /** @@ -485,16 +168,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor */ public void setFocusedMonthDateColor(int color) { - if (mFocusedMonthDateColor != color) { - mFocusedMonthDateColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasFocusedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setFocusedMonthDateColor(color); } /** @@ -505,7 +179,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor */ public int getFocusedMonthDateColor() { - return mFocusedMonthDateColor; + return mDelegate.getFocusedMonthDateColor(); } /** @@ -516,16 +190,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor */ public void setUnfocusedMonthDateColor(int color) { - if (mUnfocusedMonthDateColor != color) { - mUnfocusedMonthDateColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasUnfocusedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setUnfocusedMonthDateColor(color); } /** @@ -536,7 +201,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor */ public int getUnfocusedMonthDateColor() { - return mFocusedMonthDateColor; + return mDelegate.getUnfocusedMonthDateColor(); } /** @@ -547,12 +212,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekNumberColor */ public void setWeekNumberColor(int color) { - if (mWeekNumberColor != color) { - mWeekNumberColor = color; - if (mShowWeekNumber) { - invalidateAllWeekViews(); - } - } + mDelegate.setWeekNumberColor(color); } /** @@ -563,7 +223,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekNumberColor */ public int getWeekNumberColor() { - return mWeekNumberColor; + return mDelegate.getWeekNumberColor(); } /** @@ -574,10 +234,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor */ public void setWeekSeparatorLineColor(int color) { - if (mWeekSeparatorLineColor != color) { - mWeekSeparatorLineColor = color; - invalidateAllWeekViews(); - } + mDelegate.setWeekSeparatorLineColor(color); } /** @@ -588,7 +245,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor */ public int getWeekSeparatorLineColor() { - return mWeekSeparatorLineColor; + return mDelegate.getWeekSeparatorLineColor(); } /** @@ -600,8 +257,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar */ public void setSelectedDateVerticalBar(int resourceId) { - Drawable drawable = getResources().getDrawable(resourceId); - setSelectedDateVerticalBar(drawable); + mDelegate.setSelectedDateVerticalBar(resourceId); } /** @@ -613,16 +269,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar */ public void setSelectedDateVerticalBar(Drawable drawable) { - if (mSelectedDateVerticalBar != drawable) { - mSelectedDateVerticalBar = drawable; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasSelectedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setSelectedDateVerticalBar(drawable); } /** @@ -632,7 +279,7 @@ public class CalendarView extends FrameLayout { * @return The vertical bar drawable. */ public Drawable getSelectedDateVerticalBar() { - return mSelectedDateVerticalBar; + return mDelegate.getSelectedDateVerticalBar(); } /** @@ -643,10 +290,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance */ public void setWeekDayTextAppearance(int resourceId) { - if (mWeekDayTextAppearanceResId != resourceId) { - mWeekDayTextAppearanceResId = resourceId; - setUpHeader(); - } + mDelegate.setWeekDayTextAppearance(resourceId); } /** @@ -657,7 +301,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance */ public int getWeekDayTextAppearance() { - return mWeekDayTextAppearanceResId; + return mDelegate.getWeekDayTextAppearance(); } /** @@ -668,11 +312,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_dateTextAppearance */ public void setDateTextAppearance(int resourceId) { - if (mDateTextAppearanceResId != resourceId) { - mDateTextAppearanceResId = resourceId; - updateDateTextSize(); - invalidateAllWeekViews(); - } + mDelegate.setDateTextAppearance(resourceId); } /** @@ -683,35 +323,17 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_dateTextAppearance */ public int getDateTextAppearance() { - return mDateTextAppearanceResId; + return mDelegate.getDateTextAppearance(); } @Override public void setEnabled(boolean enabled) { - mListView.setEnabled(enabled); + mDelegate.setEnabled(enabled); } @Override public boolean isEnabled() { - return mListView.isEnabled(); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - setCurrentLocale(newConfig.locale); - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - event.setClassName(CalendarView.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(CalendarView.class.getName()); + return mDelegate.isEnabled(); } /** @@ -727,7 +349,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_minDate */ public long getMinDate() { - return mMinDate.getTimeInMillis(); + return mDelegate.getMinDate(); } /** @@ -740,30 +362,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_minDate */ public void setMinDate(long minDate) { - mTempDate.setTimeInMillis(minDate); - if (isSameDate(mTempDate, mMinDate)) { - return; - } - mMinDate.setTimeInMillis(minDate); - // make sure the current date is not earlier than - // the new min date since the latter is used for - // calculating the indices in the adapter thus - // avoiding out of bounds error - Calendar date = mAdapter.mSelectedDate; - if (date.before(mMinDate)) { - mAdapter.setSelectedDay(mMinDate); - } - // reinitialize the adapter since its range depends on min date - mAdapter.init(); - if (date.before(mMinDate)) { - setDate(mTempDate.getTimeInMillis()); - } else { - // we go to the current date to force the ListView to query its - // adapter for the shown views since we have changed the adapter - // range and the base from which the later calculates item indices - // note that calling setDate will not work since the date is the same - goTo(date, false, true, false); - } + mDelegate.setMinDate(minDate); } /** @@ -779,7 +378,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_maxDate */ public long getMaxDate() { - return mMaxDate.getTimeInMillis(); + return mDelegate.getMaxDate(); } /** @@ -792,23 +391,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_maxDate */ public void setMaxDate(long maxDate) { - mTempDate.setTimeInMillis(maxDate); - if (isSameDate(mTempDate, mMaxDate)) { - return; - } - mMaxDate.setTimeInMillis(maxDate); - // reinitialize the adapter since its range depends on max date - mAdapter.init(); - Calendar date = mAdapter.mSelectedDate; - if (date.after(mMaxDate)) { - setDate(mMaxDate.getTimeInMillis()); - } else { - // we go to the current date to force the ListView to query its - // adapter for the shown views since we have changed the adapter - // range and the base from which the later calculates item indices - // note that calling setDate will not work since the date is the same - goTo(date, false, true, false); - } + mDelegate.setMaxDate(maxDate); } /** @@ -819,12 +402,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_showWeekNumber */ public void setShowWeekNumber(boolean showWeekNumber) { - if (mShowWeekNumber == showWeekNumber) { - return; - } - mShowWeekNumber = showWeekNumber; - mAdapter.notifyDataSetChanged(); - setUpHeader(); + mDelegate.setShowWeekNumber(showWeekNumber); } /** @@ -835,7 +413,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_showWeekNumber */ public boolean getShowWeekNumber() { - return mShowWeekNumber; + return mDelegate.getShowWeekNumber(); } /** @@ -854,7 +432,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_firstDayOfWeek */ public int getFirstDayOfWeek() { - return mFirstDayOfWeek; + return mDelegate.getFirstDayOfWeek(); } /** @@ -873,12 +451,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_firstDayOfWeek */ public void setFirstDayOfWeek(int firstDayOfWeek) { - if (mFirstDayOfWeek == firstDayOfWeek) { - return; - } - mFirstDayOfWeek = firstDayOfWeek; - mAdapter.init(); - setUpHeader(); + mDelegate.setFirstDayOfWeek(firstDayOfWeek); } /** @@ -887,7 +460,7 @@ public class CalendarView extends FrameLayout { * @param listener The listener to be notified. */ public void setOnDateChangeListener(OnDateChangeListener listener) { - mOnDateChangeListener = listener; + mDelegate.setOnDateChangeListener(listener); } /** @@ -897,7 +470,7 @@ public class CalendarView extends FrameLayout { * @return The selected date. */ public long getDate() { - return mAdapter.mSelectedDate.getTimeInMillis(); + return mDelegate.getDate(); } /** @@ -914,7 +487,7 @@ public class CalendarView extends FrameLayout { * @see #setMaxDate(long) */ public void setDate(long date) { - setDate(date, false, false); + mDelegate.setDate(date); } /** @@ -932,934 +505,1645 @@ public class CalendarView extends FrameLayout { * @see #setMaxDate(long) */ public void setDate(long date, boolean animate, boolean center) { - mTempDate.setTimeInMillis(date); - if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { - return; - } - goTo(mTempDate, animate, true, center); + mDelegate.setDate(date, animate, center); } - private void updateDateTextSize() { - TypedArray dateTextAppearance = mContext.obtainStyledAttributes( - mDateTextAppearanceResId, R.styleable.TextAppearance); - mDateTextSize = dateTextAppearance.getDimensionPixelSize( - R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); - dateTextAppearance.recycle(); + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDelegate.onConfigurationChanged(newConfig); } - /** - * Invalidates all week views. - */ - private void invalidateAllWeekViews() { - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - View view = mListView.getChildAt(i); - view.invalidate(); - } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + mDelegate.onInitializeAccessibilityEvent(event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + mDelegate.onInitializeAccessibilityNodeInfo(info); } /** - * Sets the current locale. - * - * @param locale The current locale. + * A delegate interface that defined the public API of the CalendarView. Allows different + * CalendarView implementations. This would need to be implemented by the CalendarView delegates + * for the real behavior. */ - private void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; - } + private interface CalendarViewDelegate { + void setShownWeekCount(int count); + int getShownWeekCount(); - mCurrentLocale = locale; + void setSelectedWeekBackgroundColor(int color); + int getSelectedWeekBackgroundColor(); - mTempDate = getCalendarForLocale(mTempDate, locale); - mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); - mMinDate = getCalendarForLocale(mMinDate, locale); - mMaxDate = getCalendarForLocale(mMaxDate, locale); - } + void setFocusedMonthDateColor(int color); + int getFocusedMonthDateColor(); - /** - * Gets a calendar for locale bootstrapped with the value of a given calendar. - * - * @param oldCalendar The old calendar. - * @param locale The locale. - */ - private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { - if (oldCalendar == null) { - return Calendar.getInstance(locale); - } else { - final long currentTimeMillis = oldCalendar.getTimeInMillis(); - Calendar newCalendar = Calendar.getInstance(locale); - newCalendar.setTimeInMillis(currentTimeMillis); - return newCalendar; - } - } + void setUnfocusedMonthDateColor(int color); + int getUnfocusedMonthDateColor(); - /** - * @return True if the <code>firstDate</code> is the same as the <code> - * secondDate</code>. - */ - private boolean isSameDate(Calendar firstDate, Calendar secondDate) { - return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) - && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); - } + void setWeekNumberColor(int color); + int getWeekNumberColor(); - /** - * Creates a new adapter if necessary and sets up its parameters. - */ - private void setUpAdapter() { - if (mAdapter == null) { - mAdapter = new WeeksAdapter(); - mAdapter.registerDataSetObserver(new DataSetObserver() { - @Override - public void onChanged() { - if (mOnDateChangeListener != null) { - Calendar selectedDay = mAdapter.getSelectedDay(); - mOnDateChangeListener.onSelectedDayChange(CalendarView.this, - selectedDay.get(Calendar.YEAR), - selectedDay.get(Calendar.MONTH), - selectedDay.get(Calendar.DAY_OF_MONTH)); - } - } - }); - mListView.setAdapter(mAdapter); - } + void setWeekSeparatorLineColor(int color); + int getWeekSeparatorLineColor(); + + void setSelectedDateVerticalBar(int resourceId); + void setSelectedDateVerticalBar(Drawable drawable); + Drawable getSelectedDateVerticalBar(); + + void setWeekDayTextAppearance(int resourceId); + int getWeekDayTextAppearance(); + + void setDateTextAppearance(int resourceId); + int getDateTextAppearance(); + + void setEnabled(boolean enabled); + boolean isEnabled(); + + void setMinDate(long minDate); + long getMinDate(); + + void setMaxDate(long maxDate); + long getMaxDate(); + + void setShowWeekNumber(boolean showWeekNumber); + boolean getShowWeekNumber(); + + void setFirstDayOfWeek(int firstDayOfWeek); + int getFirstDayOfWeek(); + + void setDate(long date); + void setDate(long date, boolean animate, boolean center); + long getDate(); + + void setOnDateChangeListener(OnDateChangeListener listener); - // refresh the view with the new parameters - mAdapter.notifyDataSetChanged(); + void onConfigurationChanged(Configuration newConfig); + void onInitializeAccessibilityEvent(AccessibilityEvent event); + void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); } /** - * Sets up the strings to be used by the header. + * An abstract class which can be used as a start for CalendarView implementations */ - private void setUpHeader() { - final String[] tinyWeekdayNames = LocaleData.get(Locale.getDefault()).tinyWeekdayNames; - mDayLabels = new String[mDaysPerWeek]; - for (int i = 0; i < mDaysPerWeek; i++) { - final int j = i + mFirstDayOfWeek; - final int calendarDay = (j > Calendar.SATURDAY) ? j - Calendar.SATURDAY : j; - mDayLabels[i] = tinyWeekdayNames[calendarDay]; - } - // Deal with week number - TextView label = (TextView) mDayNamesHeader.getChildAt(0); - if (mShowWeekNumber) { - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); + abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate { + // The delegator + protected CalendarView mDelegator; + + // The context + protected Context mContext; + + // The current locale + protected Locale mCurrentLocale; + + AbstractCalendarViewDelegate(CalendarView delegator, Context context) { + mDelegator = delegator; + mContext = context; + + // Initialization based on locale + setCurrentLocale(Locale.getDefault()); } - // Deal with day labels - final int count = mDayNamesHeader.getChildCount(); - for (int i = 0; i < count - 1; i++) { - label = (TextView) mDayNamesHeader.getChildAt(i + 1); - if (mWeekDayTextAppearanceResId > -1) { - label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); - } - if (i < mDaysPerWeek) { - label.setText(mDayLabels[i]); - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); + + protected void setCurrentLocale(Locale locale) { + if (locale.equals(mCurrentLocale)) { + return; } + mCurrentLocale = locale; } - mDayNamesHeader.invalidate(); } /** - * Sets all the required fields for the list view. + * A delegate implementing the legacy CalendarView */ - private void setUpListView() { - // Configure the listview - mListView.setDivider(null); - mListView.setItemsCanFocus(true); - mListView.setVerticalScrollBarEnabled(false); - mListView.setOnScrollListener(new OnScrollListener() { - public void onScrollStateChanged(AbsListView view, int scrollState) { - CalendarView.this.onScrollStateChanged(view, scrollState); - } - - public void onScroll( - AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount, - totalItemCount); - } - }); - // Make the scrolling behavior nicer - mListView.setFriction(mFriction); - mListView.setVelocityScale(mVelocityScale); - } + private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate { - /** - * This moves to the specified time in the view. If the time is not already - * in range it will move the list so that the first of the month containing - * the time is at the top of the view. If the new time is already in view - * the list will not be scrolled unless forceScroll is true. This time may - * optionally be highlighted as selected as well. - * - * @param date The time to move to. - * @param animate Whether to scroll to the given time or just redraw at the - * new location. - * @param setSelected Whether to set the given time as selected. - * @param forceScroll Whether to recenter even if the time is already - * visible. - * - * @throws IllegalArgumentException of the provided date is before the - * range start of after the range end. - */ - private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) { - if (date.before(mMinDate) || date.after(mMaxDate)) { - throw new IllegalArgumentException("Time not between " + mMinDate.getTime() - + " and " + mMaxDate.getTime()); - } - // Find the first and last entirely visible weeks - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - View firstChild = mListView.getChildAt(0); - if (firstChild != null && firstChild.getTop() < 0) { - firstFullyVisiblePosition++; - } - int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; - if (firstChild != null && firstChild.getTop() > mBottomBuffer) { - lastFullyVisiblePosition--; - } - if (setSelected) { - mAdapter.setSelectedDay(date); - } - // Get the week we're going to - int position = getWeeksSinceMinDate(date); + /** + * Default value whether to show week number. + */ + private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; - // Check if the selected day is now outside of our visible range - // and if so scroll to the month that contains it - if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition - || forceScroll) { - mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); - mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); + /** + * The number of milliseconds in a day.e + */ + private static final long MILLIS_IN_DAY = 86400000L; - setMonthDisplayed(mFirstDayOfMonth); + /** + * The number of day in a week. + */ + private static final int DAYS_PER_WEEK = 7; - // the earliest time we can scroll to is the min date - if (mFirstDayOfMonth.before(mMinDate)) { - position = 0; - } else { - position = getWeeksSinceMinDate(mFirstDayOfMonth); + /** + * The number of milliseconds in a week. + */ + private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; + + /** + * Affects when the month selection will change while scrolling upe + */ + private static final int SCROLL_HYST_WEEKS = 2; + + /** + * How long the GoTo fling animation should last. + */ + private static final int GOTO_SCROLL_DURATION = 1000; + + /** + * The duration of the adjustment upon a user scroll in milliseconds. + */ + private static final int ADJUSTMENT_SCROLL_DURATION = 500; + + /** + * How long to wait after receiving an onScrollStateChanged notification + * before acting on it. + */ + private static final int SCROLL_CHANGE_DELAY = 40; + + /** + * String for parsing dates. + */ + private static final String DATE_FORMAT = "MM/dd/yyyy"; + + /** + * The default minimal date. + */ + private static final String DEFAULT_MIN_DATE = "01/01/1900"; + + /** + * The default maximal date. + */ + private static final String DEFAULT_MAX_DATE = "01/01/2100"; + + private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; + + private static final int DEFAULT_DATE_TEXT_SIZE = 14; + + private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; + + private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; + + private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; + + private static final int UNSCALED_BOTTOM_BUFFER = 20; + + private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; + + private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; + + private final int mWeekSeperatorLineWidth; + + private int mDateTextSize; + + private Drawable mSelectedDateVerticalBar; + + private final int mSelectedDateVerticalBarWidth; + + private int mSelectedWeekBackgroundColor; + + private int mFocusedMonthDateColor; + + private int mUnfocusedMonthDateColor; + + private int mWeekSeparatorLineColor; + + private int mWeekNumberColor; + + private int mWeekDayTextAppearanceResId; + + private int mDateTextAppearanceResId; + + /** + * The top offset of the weeks list. + */ + private int mListScrollTopOffset = 2; + + /** + * The visible height of a week view. + */ + private int mWeekMinVisibleHeight = 12; + + /** + * The visible height of a week view. + */ + private int mBottomBuffer = 20; + + /** + * The number of shown weeks. + */ + private int mShownWeekCount; + + /** + * Flag whether to show the week number. + */ + private boolean mShowWeekNumber; + + /** + * The number of day per week to be shown. + */ + private int mDaysPerWeek = 7; + + /** + * The friction of the week list while flinging. + */ + private float mFriction = .05f; + + /** + * Scale for adjusting velocity of the week list while flinging. + */ + private float mVelocityScale = 0.333f; + + /** + * The adapter for the weeks list. + */ + private WeeksAdapter mAdapter; + + /** + * The weeks list. + */ + private ListView mListView; + + /** + * The name of the month to display. + */ + private TextView mMonthName; + + /** + * The header with week day names. + */ + private ViewGroup mDayNamesHeader; + + /** + * Cached labels for the week names header. + */ + private String[] mDayLabels; + + /** + * The first day of the week. + */ + private int mFirstDayOfWeek; + + /** + * Which month should be displayed/highlighted [0-11]. + */ + private int mCurrentMonthDisplayed = -1; + + /** + * Used for tracking during a scroll. + */ + private long mPreviousScrollPosition; + + /** + * Used for tracking which direction the view is scrolling. + */ + private boolean mIsScrollingUp = false; + + /** + * The previous scroll state of the weeks ListView. + */ + private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * The current scroll state of the weeks ListView. + */ + private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * Listener for changes in the selected day. + */ + private OnDateChangeListener mOnDateChangeListener; + + /** + * Command for adjusting the position after a scroll/fling. + */ + private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); + + /** + * Temporary instance to avoid multiple instantiations. + */ + private Calendar mTempDate; + + /** + * The first day of the focused month. + */ + private Calendar mFirstDayOfMonth; + + /** + * The start date of the range supported by this picker. + */ + private Calendar mMinDate; + + /** + * The end date of the range supported by this picker. + */ + private Calendar mMaxDate; + + /** + * Date format for parsing dates. + */ + private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); + + LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + // initialization based on locale + setCurrentLocale(Locale.getDefault()); + + TypedArray attributesArray = context.obtainStyledAttributes(attrs, + R.styleable.CalendarView, defStyleAttr, defStyleRes); + mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, + DEFAULT_SHOW_WEEK_NUMBER); + mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, + LocaleData.get(Locale.getDefault()).firstDayOfWeek); + String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); + if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { + parseDate(DEFAULT_MIN_DATE, mMinDate); + } + String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); + if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { + parseDate(DEFAULT_MAX_DATE, mMaxDate); + } + if (mMaxDate.before(mMinDate)) { + throw new IllegalArgumentException("Max date cannot be before min date."); } + mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, + DEFAULT_SHOWN_WEEK_COUNT); + mSelectedWeekBackgroundColor = attributesArray.getColor( + R.styleable.CalendarView_selectedWeekBackgroundColor, 0); + mFocusedMonthDateColor = attributesArray.getColor( + R.styleable.CalendarView_focusedMonthDateColor, 0); + mUnfocusedMonthDateColor = attributesArray.getColor( + R.styleable.CalendarView_unfocusedMonthDateColor, 0); + mWeekSeparatorLineColor = attributesArray.getColor( + R.styleable.CalendarView_weekSeparatorLineColor, 0); + mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); + mSelectedDateVerticalBar = attributesArray.getDrawable( + R.styleable.CalendarView_selectedDateVerticalBar); + + mDateTextAppearanceResId = attributesArray.getResourceId( + R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); + updateDateTextSize(); + + mWeekDayTextAppearanceResId = attributesArray.getResourceId( + R.styleable.CalendarView_weekDayTextAppearance, + DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); + attributesArray.recycle(); + + DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); + mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); + mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); + mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_BOTTOM_BUFFER, displayMetrics); + mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); + mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); + + LayoutInflater layoutInflater = (LayoutInflater) mContext + .getSystemService(Service.LAYOUT_INFLATER_SERVICE); + View content = layoutInflater.inflate(R.layout.calendar_view, null, false); + mDelegator.addView(content); + + mListView = (ListView) mDelegator.findViewById(R.id.list); + mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); + mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); - mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; - if (animate) { - mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, - GOTO_SCROLL_DURATION); + setUpHeader(); + setUpListView(); + setUpAdapter(); + + // go to today or whichever is close to today min or max date + mTempDate.setTimeInMillis(System.currentTimeMillis()); + if (mTempDate.before(mMinDate)) { + goTo(mMinDate, false, true, true); + } else if (mMaxDate.before(mTempDate)) { + goTo(mMaxDate, false, true, true); } else { - mListView.setSelectionFromTop(position, mListScrollTopOffset); - // Perform any after scroll operations that are needed - onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); + goTo(mTempDate, false, true, true); } - } else if (setSelected) { - // Otherwise just set the selection - setMonthDisplayed(date); + + mDelegator.invalidate(); } - } - /** - * Parses the given <code>date</code> and in case of success sets - * the result to the <code>outDate</code>. - * - * @return True if the date was parsed. - */ - private boolean parseDate(String date, Calendar outDate) { - try { - outDate.setTime(mDateFormat.parse(date)); - return true; - } catch (ParseException e) { - Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); - return false; + @Override + public void setShownWeekCount(int count) { + if (mShownWeekCount != count) { + mShownWeekCount = count; + mDelegator.invalidate(); + } } - } - /** - * Called when a <code>view</code> transitions to a new <code>scrollState - * </code>. - */ - private void onScrollStateChanged(AbsListView view, int scrollState) { - mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); - } + @Override + public int getShownWeekCount() { + return mShownWeekCount; + } - /** - * Updates the title and selected month if the <code>view</code> has moved to a new - * month. - */ - private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - WeekView child = (WeekView) view.getChildAt(0); - if (child == null) { - return; + @Override + public void setSelectedWeekBackgroundColor(int color) { + if (mSelectedWeekBackgroundColor != color) { + mSelectedWeekBackgroundColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasSelectedDay) { + weekView.invalidate(); + } + } + } } - // Figure out where we are - long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); + @Override + public int getSelectedWeekBackgroundColor() { + return mSelectedWeekBackgroundColor; + } - // If we have moved since our last call update the direction - if (currScroll < mPreviousScrollPosition) { - mIsScrollingUp = true; - } else if (currScroll > mPreviousScrollPosition) { - mIsScrollingUp = false; - } else { - return; + @Override + public void setFocusedMonthDateColor(int color) { + if (mFocusedMonthDateColor != color) { + mFocusedMonthDateColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasFocusedDay) { + weekView.invalidate(); + } + } + } } - // Use some hysteresis for checking which month to highlight. This - // causes the month to transition when two full weeks of a month are - // visible when scrolling up, and when the first day in a month reaches - // the top of the screen when scrolling down. - int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; - if (mIsScrollingUp) { - child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); - } else if (offset != 0) { - child = (WeekView) view.getChildAt(offset); + @Override + public int getFocusedMonthDateColor() { + return mFocusedMonthDateColor; } - // Find out which month we're moving into - int month; - if (mIsScrollingUp) { - month = child.getMonthOfFirstWeekDay(); - } else { - month = child.getMonthOfLastWeekDay(); + @Override + public void setUnfocusedMonthDateColor(int color) { + if (mUnfocusedMonthDateColor != color) { + mUnfocusedMonthDateColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasUnfocusedDay) { + weekView.invalidate(); + } + } + } } - // And how it relates to our current highlighted month - int monthDiff; - if (mCurrentMonthDisplayed == 11 && month == 0) { - monthDiff = 1; - } else if (mCurrentMonthDisplayed == 0 && month == 11) { - monthDiff = -1; - } else { - monthDiff = month - mCurrentMonthDisplayed; + @Override + public int getUnfocusedMonthDateColor() { + return mFocusedMonthDateColor; } - // Only switch months if we're scrolling away from the currently - // selected month - if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { - Calendar firstDay = child.getFirstDay(); - if (mIsScrollingUp) { - firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); - } else { - firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); + @Override + public void setWeekNumberColor(int color) { + if (mWeekNumberColor != color) { + mWeekNumberColor = color; + if (mShowWeekNumber) { + invalidateAllWeekViews(); + } } - setMonthDisplayed(firstDay); } - mPreviousScrollPosition = currScroll; - mPreviousScrollState = mCurrentScrollState; - } - /** - * Sets the month displayed at the top of this view based on time. Override - * to add custom events when the title is changed. - * - * @param calendar A day in the new focus month. - */ - private void setMonthDisplayed(Calendar calendar) { - mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); - mAdapter.setFocusMonth(mCurrentMonthDisplayed); - final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY - | DateUtils.FORMAT_SHOW_YEAR; - final long millis = calendar.getTimeInMillis(); - String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); - mMonthName.setText(newMonthName); - mMonthName.invalidate(); - } - - /** - * @return Returns the number of weeks between the current <code>date</code> - * and the <code>mMinDate</code>. - */ - private int getWeeksSinceMinDate(Calendar date) { - if (date.before(mMinDate)) { - throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() - + " does not precede toDate: " + date.getTime()); + @Override + public int getWeekNumberColor() { + return mWeekNumberColor; } - long endTimeMillis = date.getTimeInMillis() - + date.getTimeZone().getOffset(date.getTimeInMillis()); - long startTimeMillis = mMinDate.getTimeInMillis() - + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); - long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) - * MILLIS_IN_DAY; - return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); - } - /** - * Command responsible for acting upon scroll state changes. - */ - private class ScrollStateRunnable implements Runnable { - private AbsListView mView; + @Override + public void setWeekSeparatorLineColor(int color) { + if (mWeekSeparatorLineColor != color) { + mWeekSeparatorLineColor = color; + invalidateAllWeekViews(); + } + } - private int mNewState; + @Override + public int getWeekSeparatorLineColor() { + return mWeekSeparatorLineColor; + } - /** - * Sets up the runnable with a short delay in case the scroll state - * immediately changes again. - * - * @param view The list view that changed state - * @param scrollState The new state it changed to - */ - public void doScrollStateChange(AbsListView view, int scrollState) { - mView = view; - mNewState = scrollState; - removeCallbacks(this); - postDelayed(this, SCROLL_CHANGE_DELAY); + @Override + public void setSelectedDateVerticalBar(int resourceId) { + Drawable drawable = mDelegator.getResources().getDrawable(resourceId); + setSelectedDateVerticalBar(drawable); } - public void run() { - mCurrentScrollState = mNewState; - // Fix the position after a scroll or a fling ends - if (mNewState == OnScrollListener.SCROLL_STATE_IDLE - && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { - View child = mView.getChildAt(0); - if (child == null) { - // The view is no longer visible, just return - return; - } - int dist = child.getBottom() - mListScrollTopOffset; - if (dist > mListScrollTopOffset) { - if (mIsScrollingUp) { - mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION); - } else { - mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); + @Override + public void setSelectedDateVerticalBar(Drawable drawable) { + if (mSelectedDateVerticalBar != drawable) { + mSelectedDateVerticalBar = drawable; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasSelectedDay) { + weekView.invalidate(); } } } - mPreviousScrollState = mNewState; } - } - /** - * <p> - * This is a specialized adapter for creating a list of weeks with - * selectable days. It can be configured to display the week number, start - * the week on a given day, show a reduced number of days, or display an - * arbitrary number of weeks at a time. - * </p> - */ - private class WeeksAdapter extends BaseAdapter implements OnTouchListener { - private final Calendar mSelectedDate = Calendar.getInstance(); - private final GestureDetector mGestureDetector; + @Override + public Drawable getSelectedDateVerticalBar() { + return mSelectedDateVerticalBar; + } - private int mSelectedWeek; + @Override + public void setWeekDayTextAppearance(int resourceId) { + if (mWeekDayTextAppearanceResId != resourceId) { + mWeekDayTextAppearanceResId = resourceId; + setUpHeader(); + } + } - private int mFocusedMonth; + @Override + public int getWeekDayTextAppearance() { + return mWeekDayTextAppearanceResId; + } - private int mTotalWeekCount; + @Override + public void setDateTextAppearance(int resourceId) { + if (mDateTextAppearanceResId != resourceId) { + mDateTextAppearanceResId = resourceId; + updateDateTextSize(); + invalidateAllWeekViews(); + } + } - public WeeksAdapter() { - mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); - init(); + @Override + public int getDateTextAppearance() { + return mDateTextAppearanceResId; } - /** - * Set up the gesture detector and selected time - */ - private void init() { - mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); - mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); - if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek - || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { - mTotalWeekCount++; - } - notifyDataSetChanged(); + @Override + public void setEnabled(boolean enabled) { + mListView.setEnabled(enabled); } - /** - * Updates the selected day and related parameters. - * - * @param selectedDay The time to highlight - */ - public void setSelectedDay(Calendar selectedDay) { - if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) - && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { + @Override + public boolean isEnabled() { + return mListView.isEnabled(); + } + + @Override + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (isSameDate(mTempDate, mMinDate)) { return; } - mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); - mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); - mFocusedMonth = mSelectedDate.get(Calendar.MONTH); - notifyDataSetChanged(); + mMinDate.setTimeInMillis(minDate); + // make sure the current date is not earlier than + // the new min date since the latter is used for + // calculating the indices in the adapter thus + // avoiding out of bounds error + Calendar date = mAdapter.mSelectedDate; + if (date.before(mMinDate)) { + mAdapter.setSelectedDay(mMinDate); + } + // reinitialize the adapter since its range depends on min date + mAdapter.init(); + if (date.before(mMinDate)) { + setDate(mTempDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } } - /** - * @return The selected day of month. - */ - public Calendar getSelectedDay() { - return mSelectedDate; + @Override + public long getMinDate() { + return mMinDate.getTimeInMillis(); } @Override - public int getCount() { - return mTotalWeekCount; + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (isSameDate(mTempDate, mMaxDate)) { + return; + } + mMaxDate.setTimeInMillis(maxDate); + // reinitialize the adapter since its range depends on max date + mAdapter.init(); + Calendar date = mAdapter.mSelectedDate; + if (date.after(mMaxDate)) { + setDate(mMaxDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } } @Override - public Object getItem(int position) { - return null; + public long getMaxDate() { + return mMaxDate.getTimeInMillis(); } @Override - public long getItemId(int position) { - return position; + public void setShowWeekNumber(boolean showWeekNumber) { + if (mShowWeekNumber == showWeekNumber) { + return; + } + mShowWeekNumber = showWeekNumber; + mAdapter.notifyDataSetChanged(); + setUpHeader(); } @Override - public View getView(int position, View convertView, ViewGroup parent) { - WeekView weekView = null; - if (convertView != null) { - weekView = (WeekView) convertView; - } else { - weekView = new WeekView(mContext); - android.widget.AbsListView.LayoutParams params = - new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - weekView.setLayoutParams(params); - weekView.setClickable(true); - weekView.setOnTouchListener(this); + public boolean getShowWeekNumber() { + return mShowWeekNumber; + } + + @Override + public void setFirstDayOfWeek(int firstDayOfWeek) { + if (mFirstDayOfWeek == firstDayOfWeek) { + return; } + mFirstDayOfWeek = firstDayOfWeek; + mAdapter.init(); + mAdapter.notifyDataSetChanged(); + setUpHeader(); + } - int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( - Calendar.DAY_OF_WEEK) : -1; - weekView.init(position, selectedWeekDay, mFocusedMonth); + @Override + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } - return weekView; + @Override + public void setDate(long date) { + setDate(date, false, false); } - /** - * Changes which month is in focus and updates the view. - * - * @param month The month to show as in focus [0-11] - */ - public void setFocusMonth(int month) { - if (mFocusedMonth == month) { + @Override + public void setDate(long date, boolean animate, boolean center) { + mTempDate.setTimeInMillis(date); + if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { return; } - mFocusedMonth = month; - notifyDataSetChanged(); + goTo(mTempDate, animate, true, center); } @Override - public boolean onTouch(View v, MotionEvent event) { - if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { - WeekView weekView = (WeekView) v; - // if we cannot find a day for the given location we are done - if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { - return true; - } - // it is possible that the touched day is outside the valid range - // we draw whole weeks but range end can fall not on the week end - if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { - return true; - } - onDateTapped(mTempDate); - return true; - } - return false; + public long getDate() { + return mAdapter.mSelectedDate.getTimeInMillis(); + } + + @Override + public void setOnDateChangeListener(OnDateChangeListener listener) { + mOnDateChangeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + setCurrentLocale(newConfig.locale); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(CalendarView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + info.setClassName(CalendarView.class.getName()); } /** - * Maintains the same hour/min/sec but moves the day to the tapped day. + * Sets the current locale. * - * @param day The day that was tapped + * @param locale The current locale. */ - private void onDateTapped(Calendar day) { - setSelectedDay(day); - setMonthDisplayed(day); + @Override + protected void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + + mTempDate = getCalendarForLocale(mTempDate, locale); + mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); + mMinDate = getCalendarForLocale(mMinDate, locale); + mMaxDate = getCalendarForLocale(mMaxDate, locale); + } + private void updateDateTextSize() { + TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( + mDateTextAppearanceResId, R.styleable.TextAppearance); + mDateTextSize = dateTextAppearance.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); + dateTextAppearance.recycle(); } /** - * This is here so we can identify single tap events and set the - * selected day correctly + * Invalidates all week views. */ - class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onSingleTapUp(MotionEvent e) { - return true; + private void invalidateAllWeekViews() { + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = mListView.getChildAt(i); + view.invalidate(); } } - } - /** - * <p> - * This is a dynamic view for drawing a single week. It can be configured to - * display the week number, start the week on a given day, or show a reduced - * number of days. It is intended for use as a single view within a - * ListView. See {@link WeeksAdapter} for usage. - * </p> - */ - private class WeekView extends View { + /** + * Gets a calendar for locale bootstrapped with the value of a given calendar. + * + * @param oldCalendar The old calendar. + * @param locale The locale. + */ + private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { + if (oldCalendar == null) { + return Calendar.getInstance(locale); + } else { + final long currentTimeMillis = oldCalendar.getTimeInMillis(); + Calendar newCalendar = Calendar.getInstance(locale); + newCalendar.setTimeInMillis(currentTimeMillis); + return newCalendar; + } + } - private final Rect mTempRect = new Rect(); + /** + * @return True if the <code>firstDate</code> is the same as the <code> + * secondDate</code>. + */ + private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { + return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) + && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); + } - private final Paint mDrawPaint = new Paint(); + /** + * Creates a new adapter if necessary and sets up its parameters. + */ + private void setUpAdapter() { + if (mAdapter == null) { + mAdapter = new WeeksAdapter(mContext); + mAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + if (mOnDateChangeListener != null) { + Calendar selectedDay = mAdapter.getSelectedDay(); + mOnDateChangeListener.onSelectedDayChange(mDelegator, + selectedDay.get(Calendar.YEAR), + selectedDay.get(Calendar.MONTH), + selectedDay.get(Calendar.DAY_OF_MONTH)); + } + } + }); + mListView.setAdapter(mAdapter); + } - private final Paint mMonthNumDrawPaint = new Paint(); + // refresh the view with the new parameters + mAdapter.notifyDataSetChanged(); + } - // Cache the number strings so we don't have to recompute them each time - private String[] mDayNumbers; + /** + * Sets up the strings to be used by the header. + */ + private void setUpHeader() { + mDayLabels = new String[mDaysPerWeek]; + for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { + int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; + mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, + DateUtils.LENGTH_SHORTEST); + } - // Quick lookup for checking which days are in the focus month - private boolean[] mFocusDay; + TextView label = (TextView) mDayNamesHeader.getChildAt(0); + if (mShowWeekNumber) { + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } + for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { + label = (TextView) mDayNamesHeader.getChildAt(i); + if (mWeekDayTextAppearanceResId > -1) { + label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); + } + if (i < mDaysPerWeek + 1) { + label.setText(mDayLabels[i - 1]); + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } + } + mDayNamesHeader.invalidate(); + } - // Whether this view has a focused day. - private boolean mHasFocusedDay; + /** + * Sets all the required fields for the list view. + */ + private void setUpListView() { + // Configure the listview + mListView.setDivider(null); + mListView.setItemsCanFocus(true); + mListView.setVerticalScrollBarEnabled(false); + mListView.setOnScrollListener(new OnScrollListener() { + public void onScrollStateChanged(AbsListView view, int scrollState) { + LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState); + } - // Whether this view has only focused days. - private boolean mHasUnfocusedDay; + public void onScroll( + AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem, + visibleItemCount, totalItemCount); + } + }); + // Make the scrolling behavior nicer + mListView.setFriction(mFriction); + mListView.setVelocityScale(mVelocityScale); + } - // The first day displayed by this item - private Calendar mFirstDay; + /** + * This moves to the specified time in the view. If the time is not already + * in range it will move the list so that the first of the month containing + * the time is at the top of the view. If the new time is already in view + * the list will not be scrolled unless forceScroll is true. This time may + * optionally be highlighted as selected as well. + * + * @param date The time to move to. + * @param animate Whether to scroll to the given time or just redraw at the + * new location. + * @param setSelected Whether to set the given time as selected. + * @param forceScroll Whether to recenter even if the time is already + * visible. + * + * @throws IllegalArgumentException of the provided date is before the + * range start of after the range end. + */ + private void goTo(Calendar date, boolean animate, boolean setSelected, + boolean forceScroll) { + if (date.before(mMinDate) || date.after(mMaxDate)) { + throw new IllegalArgumentException("Time not between " + mMinDate.getTime() + + " and " + mMaxDate.getTime()); + } + // Find the first and last entirely visible weeks + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + View firstChild = mListView.getChildAt(0); + if (firstChild != null && firstChild.getTop() < 0) { + firstFullyVisiblePosition++; + } + int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; + if (firstChild != null && firstChild.getTop() > mBottomBuffer) { + lastFullyVisiblePosition--; + } + if (setSelected) { + mAdapter.setSelectedDay(date); + } + // Get the week we're going to + int position = getWeeksSinceMinDate(date); - // The month of the first day in this week - private int mMonthOfFirstWeekDay = -1; + // Check if the selected day is now outside of our visible range + // and if so scroll to the month that contains it + if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition + || forceScroll) { + mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); + mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); - // The month of the last day in this week - private int mLastWeekDayMonth = -1; + setMonthDisplayed(mFirstDayOfMonth); - // The position of this week, equivalent to weeks since the week of Jan - // 1st, 1900 - private int mWeek = -1; + // the earliest time we can scroll to is the min date + if (mFirstDayOfMonth.before(mMinDate)) { + position = 0; + } else { + position = getWeeksSinceMinDate(mFirstDayOfMonth); + } - // Quick reference to the width of this view, matches parent - private int mWidth; + mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; + if (animate) { + mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, + GOTO_SCROLL_DURATION); + } else { + mListView.setSelectionFromTop(position, mListScrollTopOffset); + // Perform any after scroll operations that are needed + onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); + } + } else if (setSelected) { + // Otherwise just set the selection + setMonthDisplayed(date); + } + } - // The height this view should draw at in pixels, set by height param - private int mHeight; + /** + * Parses the given <code>date</code> and in case of success sets + * the result to the <code>outDate</code>. + * + * @return True if the date was parsed. + */ + private boolean parseDate(String date, Calendar outDate) { + try { + outDate.setTime(mDateFormat.parse(date)); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; + } + } - // If this view contains the selected day - private boolean mHasSelectedDay = false; + /** + * Called when a <code>view</code> transitions to a new <code>scrollState + * </code>. + */ + private void onScrollStateChanged(AbsListView view, int scrollState) { + mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); + } - // Which day is selected [0-6] or -1 if no day is selected - private int mSelectedDay = -1; + /** + * Updates the title and selected month if the <code>view</code> has moved to a new + * month. + */ + private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + WeekView child = (WeekView) view.getChildAt(0); + if (child == null) { + return; + } - // The number of days + a spot for week number if it is displayed - private int mNumCells; + // Figure out where we are + long currScroll = + view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); - // The left edge of the selected day - private int mSelectedLeft = -1; + // If we have moved since our last call update the direction + if (currScroll < mPreviousScrollPosition) { + mIsScrollingUp = true; + } else if (currScroll > mPreviousScrollPosition) { + mIsScrollingUp = false; + } else { + return; + } - // The right edge of the selected day - private int mSelectedRight = -1; + // Use some hysteresis for checking which month to highlight. This + // causes the month to transition when two full weeks of a month are + // visible when scrolling up, and when the first day in a month reaches + // the top of the screen when scrolling down. + int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; + if (mIsScrollingUp) { + child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); + } else if (offset != 0) { + child = (WeekView) view.getChildAt(offset); + } - public WeekView(Context context) { - super(context); + // Find out which month we're moving into + int month; + if (mIsScrollingUp) { + month = child.getMonthOfFirstWeekDay(); + } else { + month = child.getMonthOfLastWeekDay(); + } - // Sets up any standard paints that will be used - initilaizePaints(); - } + // And how it relates to our current highlighted month + int monthDiff; + if (mCurrentMonthDisplayed == 11 && month == 0) { + monthDiff = 1; + } else if (mCurrentMonthDisplayed == 0 && month == 11) { + monthDiff = -1; + } else { + monthDiff = month - mCurrentMonthDisplayed; + } - /** - * Initializes this week view. - * - * @param weekNumber The number of the week this view represents. The - * week number is a zero based index of the weeks since - * {@link CalendarView#getMinDate()}. - * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no - * selected day. - * @param focusedMonth The month that is currently in focus i.e. - * highlighted. - */ - public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { - mSelectedDay = selectedWeekDay; - mHasSelectedDay = mSelectedDay != -1; - mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; - mWeek = weekNumber; - mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); - - mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); - mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); - - // Allocate space for caching the day numbers and focus values - mDayNumbers = new String[mNumCells]; - mFocusDay = new boolean[mNumCells]; - - // If we're showing the week number calculate it based on Monday - int i = 0; - if (mShowWeekNumber) { - mDayNumbers[0] = String.format(Locale.getDefault(), "%d", - mTempDate.get(Calendar.WEEK_OF_YEAR)); - i++; - } - - // Now adjust our starting day based on the start day of the week - int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); - mTempDate.add(Calendar.DAY_OF_MONTH, diff); - - mFirstDay = (Calendar) mTempDate.clone(); - mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); - - mHasUnfocusedDay = true; - for (; i < mNumCells; i++) { - final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); - mFocusDay[i] = isFocusedDay; - mHasFocusedDay |= isFocusedDay; - mHasUnfocusedDay &= !isFocusedDay; - // do not draw dates outside the valid range to avoid user confusion - if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { - mDayNumbers[i] = ""; + // Only switch months if we're scrolling away from the currently + // selected month + if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { + Calendar firstDay = child.getFirstDay(); + if (mIsScrollingUp) { + firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); } else { - mDayNumbers[i] = String.format(Locale.getDefault(), "%d", - mTempDate.get(Calendar.DAY_OF_MONTH)); + firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); } - mTempDate.add(Calendar.DAY_OF_MONTH, 1); - } - // We do one extra add at the end of the loop, if that pushed us to - // new month undo it - if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { - mTempDate.add(Calendar.DAY_OF_MONTH, -1); + setMonthDisplayed(firstDay); } - mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); - - updateSelectionPositions(); + mPreviousScrollPosition = currScroll; + mPreviousScrollState = mCurrentScrollState; } /** - * Initialize the paint instances. - */ - private void initilaizePaints() { - mDrawPaint.setFakeBoldText(false); - mDrawPaint.setAntiAlias(true); - mDrawPaint.setStyle(Style.FILL); - - mMonthNumDrawPaint.setFakeBoldText(true); - mMonthNumDrawPaint.setAntiAlias(true); - mMonthNumDrawPaint.setStyle(Style.FILL); - mMonthNumDrawPaint.setTextAlign(Align.CENTER); - mMonthNumDrawPaint.setTextSize(mDateTextSize); - } - - /** - * Returns the month of the first day in this week. + * Sets the month displayed at the top of this view based on time. Override + * to add custom events when the title is changed. * - * @return The month the first day of this view is in. + * @param calendar A day in the new focus month. */ - public int getMonthOfFirstWeekDay() { - return mMonthOfFirstWeekDay; + private void setMonthDisplayed(Calendar calendar) { + mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); + mAdapter.setFocusMonth(mCurrentMonthDisplayed); + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY + | DateUtils.FORMAT_SHOW_YEAR; + final long millis = calendar.getTimeInMillis(); + String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); + mMonthName.setText(newMonthName); + mMonthName.invalidate(); } /** - * Returns the month of the last day in this week - * - * @return The month the last day of this view is in + * @return Returns the number of weeks between the current <code>date</code> + * and the <code>mMinDate</code>. */ - public int getMonthOfLastWeekDay() { - return mLastWeekDayMonth; + private int getWeeksSinceMinDate(Calendar date) { + if (date.before(mMinDate)) { + throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() + + " does not precede toDate: " + date.getTime()); + } + long endTimeMillis = date.getTimeInMillis() + + date.getTimeZone().getOffset(date.getTimeInMillis()); + long startTimeMillis = mMinDate.getTimeInMillis() + + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); + long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) + * MILLIS_IN_DAY; + return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); } /** - * Returns the first day in this view. - * - * @return The first day in the view. + * Command responsible for acting upon scroll state changes. */ - public Calendar getFirstDay() { - return mFirstDay; + private class ScrollStateRunnable implements Runnable { + private AbsListView mView; + + private int mNewState; + + /** + * Sets up the runnable with a short delay in case the scroll state + * immediately changes again. + * + * @param view The list view that changed state + * @param scrollState The new state it changed to + */ + public void doScrollStateChange(AbsListView view, int scrollState) { + mView = view; + mNewState = scrollState; + mDelegator.removeCallbacks(this); + mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); + } + + public void run() { + mCurrentScrollState = mNewState; + // Fix the position after a scroll or a fling ends + if (mNewState == OnScrollListener.SCROLL_STATE_IDLE + && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { + View child = mView.getChildAt(0); + if (child == null) { + // The view is no longer visible, just return + return; + } + int dist = child.getBottom() - mListScrollTopOffset; + if (dist > mListScrollTopOffset) { + if (mIsScrollingUp) { + mView.smoothScrollBy(dist - child.getHeight(), + ADJUSTMENT_SCROLL_DURATION); + } else { + mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); + } + } + } + mPreviousScrollState = mNewState; + } } /** - * Calculates the day that the given x position is in, accounting for - * week number. - * - * @param x The x position of the touch event. - * @return True if a day was found for the given location. + * <p> + * This is a specialized adapter for creating a list of weeks with + * selectable days. It can be configured to display the week number, start + * the week on a given day, show a reduced number of days, or display an + * arbitrary number of weeks at a time. + * </p> */ - public boolean getDayFromLocation(float x, Calendar outCalendar) { - final boolean isLayoutRtl = isLayoutRtl(); + private class WeeksAdapter extends BaseAdapter implements OnTouchListener { - int start; - int end; + private int mSelectedWeek; - if (isLayoutRtl) { - start = 0; - end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - start = mShowWeekNumber ? mWidth / mNumCells : 0; - end = mWidth; + private GestureDetector mGestureDetector; + + private int mFocusedMonth; + + private final Calendar mSelectedDate = Calendar.getInstance(); + + private int mTotalWeekCount; + + public WeeksAdapter(Context context) { + mContext = context; + mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); + init(); } - if (x < start || x > end) { - outCalendar.clear(); - return false; + /** + * Set up the gesture detector and selected time + */ + private void init() { + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); + if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek + || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { + mTotalWeekCount++; + } + } + + /** + * Updates the selected day and related parameters. + * + * @param selectedDay The time to highlight + */ + public void setSelectedDay(Calendar selectedDay) { + if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) + && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { + return; + } + mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mFocusedMonth = mSelectedDate.get(Calendar.MONTH); + notifyDataSetChanged(); + } + + /** + * @return The selected day of month. + */ + public Calendar getSelectedDay() { + return mSelectedDate; } - // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels - int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); + @Override + public int getCount() { + return mTotalWeekCount; + } - if (isLayoutRtl) { - dayPosition = mDaysPerWeek - 1 - dayPosition; + @Override + public Object getItem(int position) { + return null; } - outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); - outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + @Override + public long getItemId(int position) { + return position; + } - return true; - } + @Override + public View getView(int position, View convertView, ViewGroup parent) { + WeekView weekView = null; + if (convertView != null) { + weekView = (WeekView) convertView; + } else { + weekView = new WeekView(mContext); + android.widget.AbsListView.LayoutParams params = + new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + weekView.setLayoutParams(params); + weekView.setClickable(true); + weekView.setOnTouchListener(this); + } - @Override - protected void onDraw(Canvas canvas) { - drawBackground(canvas); - drawWeekNumbersAndDates(canvas); - drawWeekSeparators(canvas); - drawSelectedDateVerticalBars(canvas); - } + int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( + Calendar.DAY_OF_WEEK) : -1; + weekView.init(position, selectedWeekDay, mFocusedMonth); - /** - * This draws the selection highlight if a day is selected in this week. - * - * @param canvas The canvas to draw on - */ - private void drawBackground(Canvas canvas) { - if (!mHasSelectedDay) { - return; + return weekView; } - mDrawPaint.setColor(mSelectedWeekBackgroundColor); - mTempRect.top = mWeekSeperatorLineWidth; - mTempRect.bottom = mHeight; + /** + * Changes which month is in focus and updates the view. + * + * @param month The month to show as in focus [0-11] + */ + public void setFocusMonth(int month) { + if (mFocusedMonth == month) { + return; + } + mFocusedMonth = month; + notifyDataSetChanged(); + } - final boolean isLayoutRtl = isLayoutRtl(); + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { + WeekView weekView = (WeekView) v; + // if we cannot find a day for the given location we are done + if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { + return true; + } + // it is possible that the touched day is outside the valid range + // we draw whole weeks but range end can fall not on the week end + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + return true; + } + onDateTapped(mTempDate); + return true; + } + return false; + } - if (isLayoutRtl) { - mTempRect.left = 0; - mTempRect.right = mSelectedLeft - 2; - } else { - mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; - mTempRect.right = mSelectedLeft - 2; + /** + * Maintains the same hour/min/sec but moves the day to the tapped day. + * + * @param day The day that was tapped + */ + private void onDateTapped(Calendar day) { + setSelectedDay(day); + setMonthDisplayed(day); } - canvas.drawRect(mTempRect, mDrawPaint); - if (isLayoutRtl) { - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mWidth; + /** + * This is here so we can identify single tap events and set the + * selected day correctly + */ + class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } } - canvas.drawRect(mTempRect, mDrawPaint); } /** - * Draws the week and month day numbers for this week. - * - * @param canvas The canvas to draw on + * <p> + * This is a dynamic view for drawing a single week. It can be configured to + * display the week number, start the week on a given day, or show a reduced + * number of days. It is intended for use as a single view within a + * ListView. See {@link WeeksAdapter} for usage. + * </p> */ - private void drawWeekNumbersAndDates(Canvas canvas) { - final float textHeight = mDrawPaint.getTextSize(); - final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; - final int nDays = mNumCells; - final int divisor = 2 * nDays; - - mDrawPaint.setTextAlign(Align.CENTER); - mDrawPaint.setTextSize(mDateTextSize); - - int i = 0; - - if (isLayoutRtl()) { - for (; i < nDays - 1; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); - } - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth - mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); - } - } else { + private class WeekView extends View { + + private final Rect mTempRect = new Rect(); + + private final Paint mDrawPaint = new Paint(); + + private final Paint mMonthNumDrawPaint = new Paint(); + + // Cache the number strings so we don't have to recompute them each time + private String[] mDayNumbers; + + // Quick lookup for checking which days are in the focus month + private boolean[] mFocusDay; + + // Whether this view has a focused day. + private boolean mHasFocusedDay; + + // Whether this view has only focused days. + private boolean mHasUnfocusedDay; + + // The first day displayed by this item + private Calendar mFirstDay; + + // The month of the first day in this week + private int mMonthOfFirstWeekDay = -1; + + // The month of the last day in this week + private int mLastWeekDayMonth = -1; + + // The position of this week, equivalent to weeks since the week of Jan + // 1st, 1900 + private int mWeek = -1; + + // Quick reference to the width of this view, matches parent + private int mWidth; + + // The height this view should draw at in pixels, set by height param + private int mHeight; + + // If this view contains the selected day + private boolean mHasSelectedDay = false; + + // Which day is selected [0-6] or -1 if no day is selected + private int mSelectedDay = -1; + + // The number of days + a spot for week number if it is displayed + private int mNumCells; + + // The left edge of the selected day + private int mSelectedLeft = -1; + + // The right edge of the selected day + private int mSelectedRight = -1; + + public WeekView(Context context) { + super(context); + + // Sets up any standard paints that will be used + initilaizePaints(); + } + + /** + * Initializes this week view. + * + * @param weekNumber The number of the week this view represents. The + * week number is a zero based index of the weeks since + * {@link CalendarView#getMinDate()}. + * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no + * selected day. + * @param focusedMonth The month that is currently in focus i.e. + * highlighted. + */ + public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { + mSelectedDay = selectedWeekDay; + mHasSelectedDay = mSelectedDay != -1; + mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; + mWeek = weekNumber; + mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); + + mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); + mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); + + // Allocate space for caching the day numbers and focus values + mDayNumbers = new String[mNumCells]; + mFocusDay = new boolean[mNumCells]; + + // If we're showing the week number calculate it based on Monday + int i = 0; if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + mDayNumbers[0] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.WEEK_OF_YEAR)); i++; } - for (; i < nDays; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + + // Now adjust our starting day based on the start day of the week + int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); + mTempDate.add(Calendar.DAY_OF_MONTH, diff); + + mFirstDay = (Calendar) mTempDate.clone(); + mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); + + mHasUnfocusedDay = true; + for (; i < mNumCells; i++) { + final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); + mFocusDay[i] = isFocusedDay; + mHasFocusedDay |= isFocusedDay; + mHasUnfocusedDay &= !isFocusedDay; + // do not draw dates outside the valid range to avoid user confusion + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + mDayNumbers[i] = ""; + } else { + mDayNumbers[i] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.DAY_OF_MONTH)); + } + mTempDate.add(Calendar.DAY_OF_MONTH, 1); } - } - } + // We do one extra add at the end of the loop, if that pushed us to + // new month undo it + if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { + mTempDate.add(Calendar.DAY_OF_MONTH, -1); + } + mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); - /** - * Draws a horizontal line for separating the weeks. - * - * @param canvas The canvas to draw on. - */ - private void drawWeekSeparators(Canvas canvas) { - // If it is the topmost fully visible child do not draw separator line - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - if (mListView.getChildAt(0).getTop() < 0) { - firstFullyVisiblePosition++; + updateSelectionPositions(); } - if (firstFullyVisiblePosition == mWeek) { - return; + + /** + * Initialize the paint instances. + */ + private void initilaizePaints() { + mDrawPaint.setFakeBoldText(false); + mDrawPaint.setAntiAlias(true); + mDrawPaint.setStyle(Style.FILL); + + mMonthNumDrawPaint.setFakeBoldText(true); + mMonthNumDrawPaint.setAntiAlias(true); + mMonthNumDrawPaint.setStyle(Style.FILL); + mMonthNumDrawPaint.setTextAlign(Align.CENTER); + mMonthNumDrawPaint.setTextSize(mDateTextSize); } - mDrawPaint.setColor(mWeekSeparatorLineColor); - mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); - float startX; - float stopX; - if (isLayoutRtl()) { - startX = 0; - stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - startX = mShowWeekNumber ? mWidth / mNumCells : 0; - stopX = mWidth; + + /** + * Returns the month of the first day in this week. + * + * @return The month the first day of this view is in. + */ + public int getMonthOfFirstWeekDay() { + return mMonthOfFirstWeekDay; } - canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); - } - /** - * Draws the selected date bars if this week has a selected day. - * - * @param canvas The canvas to draw on - */ - private void drawSelectedDateVerticalBars(Canvas canvas) { - if (!mHasSelectedDay) { - return; + /** + * Returns the month of the last day in this week + * + * @return The month the last day of this view is in + */ + public int getMonthOfLastWeekDay() { + return mLastWeekDayMonth; } - mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2, - mWeekSeperatorLineWidth, - mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight); - mSelectedDateVerticalBar.draw(canvas); - mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2, - mWeekSeperatorLineWidth, - mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight); - mSelectedDateVerticalBar.draw(canvas); - } - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; - updateSelectionPositions(); - } + /** + * Returns the first day in this view. + * + * @return The first day in the view. + */ + public Calendar getFirstDay() { + return mFirstDay; + } - /** - * This calculates the positions for the selected day lines. - */ - private void updateSelectionPositions() { - if (mHasSelectedDay) { + /** + * Calculates the day that the given x position is in, accounting for + * week number. + * + * @param x The x position of the touch event. + * @return True if a day was found for the given location. + */ + public boolean getDayFromLocation(float x, Calendar outCalendar) { final boolean isLayoutRtl = isLayoutRtl(); - int selectedPosition = mSelectedDay - mFirstDayOfWeek; - if (selectedPosition < 0) { - selectedPosition += 7; + + int start; + int end; + + if (isLayoutRtl) { + start = 0; + end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + start = mShowWeekNumber ? mWidth / mNumCells : 0; + end = mWidth; } - if (mShowWeekNumber && !isLayoutRtl) { - selectedPosition++; + + if (x < start || x > end) { + outCalendar.clear(); + return false; } + + // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels + int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); + if (isLayoutRtl) { - mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; + dayPosition = mDaysPerWeek - 1 - dayPosition; + } + outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); + outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + drawBackground(canvas); + drawWeekNumbersAndDates(canvas); + drawWeekSeparators(canvas); + drawSelectedDateVerticalBars(canvas); + } + + /** + * This draws the selection highlight if a day is selected in this week. + * + * @param canvas The canvas to draw on + */ + private void drawBackground(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mDrawPaint.setColor(mSelectedWeekBackgroundColor); + + mTempRect.top = mWeekSeperatorLineWidth; + mTempRect.bottom = mHeight; + + final boolean isLayoutRtl = isLayoutRtl(); + + if (isLayoutRtl) { + mTempRect.left = 0; + mTempRect.right = mSelectedLeft - 2; + } else { + mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; + mTempRect.right = mSelectedLeft - 2; + } + canvas.drawRect(mTempRect, mDrawPaint); + + if (isLayoutRtl) { + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; } else { - mSelectedLeft = selectedPosition * mWidth / mNumCells; + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mWidth; } - mSelectedRight = mSelectedLeft + mWidth / mNumCells; + canvas.drawRect(mTempRect, mDrawPaint); } - } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView - .getPaddingBottom()) / mShownWeekCount; - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); + /** + * Draws the week and month day numbers for this week. + * + * @param canvas The canvas to draw on + */ + private void drawWeekNumbersAndDates(Canvas canvas) { + final float textHeight = mDrawPaint.getTextSize(); + final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; + final int nDays = mNumCells; + final int divisor = 2 * nDays; + + mDrawPaint.setTextAlign(Align.CENTER); + mDrawPaint.setTextSize(mDateTextSize); + + int i = 0; + + if (isLayoutRtl()) { + for (; i < nDays - 1; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); + } + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth - mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + } + } else { + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + i++; + } + for (; i < nDays; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + } + } + } + + /** + * Draws a horizontal line for separating the weeks. + * + * @param canvas The canvas to draw on. + */ + private void drawWeekSeparators(Canvas canvas) { + // If it is the topmost fully visible child do not draw separator line + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + if (mListView.getChildAt(0).getTop() < 0) { + firstFullyVisiblePosition++; + } + if (firstFullyVisiblePosition == mWeek) { + return; + } + mDrawPaint.setColor(mWeekSeparatorLineColor); + mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); + float startX; + float stopX; + if (isLayoutRtl()) { + startX = 0; + stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + startX = mShowWeekNumber ? mWidth / mNumCells : 0; + stopX = mWidth; + } + canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); + } + + /** + * Draws the selected date bars if this week has a selected day. + * + * @param canvas The canvas to draw on + */ + private void drawSelectedDateVerticalBars(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mSelectedDateVerticalBar.setBounds( + mSelectedLeft - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedLeft + mSelectedDateVerticalBarWidth / 2, + mHeight); + mSelectedDateVerticalBar.draw(canvas); + mSelectedDateVerticalBar.setBounds( + mSelectedRight - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedRight + mSelectedDateVerticalBarWidth / 2, + mHeight); + mSelectedDateVerticalBar.draw(canvas); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + updateSelectionPositions(); + } + + /** + * This calculates the positions for the selected day lines. + */ + private void updateSelectionPositions() { + if (mHasSelectedDay) { + final boolean isLayoutRtl = isLayoutRtl(); + int selectedPosition = mSelectedDay - mFirstDayOfWeek; + if (selectedPosition < 0) { + selectedPosition += 7; + } + if (mShowWeekNumber && !isLayoutRtl) { + selectedPosition++; + } + if (isLayoutRtl) { + mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; + + } else { + mSelectedLeft = selectedPosition * mWidth / mNumCells; + } + mSelectedRight = mSelectedLeft + mWidth / mNumCells; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView + .getPaddingBottom()) / mShownWeekCount; + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); + } } + } + } |
