blob: 2ab35482bf567788f4c12d06634758a005ff7531 [file] [log] [blame]
Alan Viverette46127402014-11-13 10:50:37 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import com.android.internal.R;
20
21import android.app.Service;
22import android.content.Context;
23import android.content.res.Configuration;
24import android.content.res.TypedArray;
25import android.database.DataSetObserver;
26import android.graphics.Canvas;
27import android.graphics.Paint;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.text.TextUtils;
31import android.text.format.DateUtils;
32import android.util.AttributeSet;
33import android.util.DisplayMetrics;
34import android.util.TypedValue;
35import android.view.GestureDetector;
36import android.view.LayoutInflater;
37import android.view.MotionEvent;
38import android.view.View;
39import android.view.ViewGroup;
40
41import java.util.Calendar;
42import java.util.Locale;
43
44import libcore.icu.LocaleData;
45
46/**
47 * A delegate implementing the legacy CalendarView
48 */
49class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate {
50 /**
51 * Default value whether to show week number.
52 */
53 private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
54
55 /**
56 * The number of milliseconds in a day.e
57 */
58 private static final long MILLIS_IN_DAY = 86400000L;
59
60 /**
61 * The number of day in a week.
62 */
63 private static final int DAYS_PER_WEEK = 7;
64
65 /**
66 * The number of milliseconds in a week.
67 */
68 private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
69
70 /**
71 * Affects when the month selection will change while scrolling upe
72 */
73 private static final int SCROLL_HYST_WEEKS = 2;
74
75 /**
76 * How long the GoTo fling animation should last.
77 */
78 private static final int GOTO_SCROLL_DURATION = 1000;
79
80 /**
81 * The duration of the adjustment upon a user scroll in milliseconds.
82 */
83 private static final int ADJUSTMENT_SCROLL_DURATION = 500;
84
85 /**
86 * How long to wait after receiving an onScrollStateChanged notification
87 * before acting on it.
88 */
89 private static final int SCROLL_CHANGE_DELAY = 40;
90
91 private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
92
93 private static final int DEFAULT_DATE_TEXT_SIZE = 14;
94
95 private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
96
97 private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
98
99 private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
100
101 private static final int UNSCALED_BOTTOM_BUFFER = 20;
102
103 private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
104
105 private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
106
107 private final int mWeekSeperatorLineWidth;
108
109 private int mDateTextSize;
110
111 private Drawable mSelectedDateVerticalBar;
112
113 private final int mSelectedDateVerticalBarWidth;
114
115 private int mSelectedWeekBackgroundColor;
116
117 private int mFocusedMonthDateColor;
118
119 private int mUnfocusedMonthDateColor;
120
121 private int mWeekSeparatorLineColor;
122
123 private int mWeekNumberColor;
124
125 private int mWeekDayTextAppearanceResId;
126
127 private int mDateTextAppearanceResId;
128
129 /**
130 * The top offset of the weeks list.
131 */
132 private int mListScrollTopOffset = 2;
133
134 /**
135 * The visible height of a week view.
136 */
137 private int mWeekMinVisibleHeight = 12;
138
139 /**
140 * The visible height of a week view.
141 */
142 private int mBottomBuffer = 20;
143
144 /**
145 * The number of shown weeks.
146 */
147 private int mShownWeekCount;
148
149 /**
150 * Flag whether to show the week number.
151 */
152 private boolean mShowWeekNumber;
153
154 /**
155 * The number of day per week to be shown.
156 */
157 private int mDaysPerWeek = 7;
158
159 /**
160 * The friction of the week list while flinging.
161 */
162 private float mFriction = .05f;
163
164 /**
165 * Scale for adjusting velocity of the week list while flinging.
166 */
167 private float mVelocityScale = 0.333f;
168
169 /**
170 * The adapter for the weeks list.
171 */
172 private WeeksAdapter mAdapter;
173
174 /**
175 * The weeks list.
176 */
177 private ListView mListView;
178
179 /**
180 * The name of the month to display.
181 */
182 private TextView mMonthName;
183
184 /**
185 * The header with week day names.
186 */
187 private ViewGroup mDayNamesHeader;
188
189 /**
190 * Cached abbreviations for day of week names.
191 */
192 private String[] mDayNamesShort;
193
194 /**
195 * Cached full-length day of week names.
196 */
197 private String[] mDayNamesLong;
198
199 /**
200 * The first day of the week.
201 */
202 private int mFirstDayOfWeek;
203
204 /**
205 * Which month should be displayed/highlighted [0-11].
206 */
207 private int mCurrentMonthDisplayed = -1;
208
209 /**
210 * Used for tracking during a scroll.
211 */
212 private long mPreviousScrollPosition;
213
214 /**
215 * Used for tracking which direction the view is scrolling.
216 */
217 private boolean mIsScrollingUp = false;
218
219 /**
220 * The previous scroll state of the weeks ListView.
221 */
222 private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
223
224 /**
225 * The current scroll state of the weeks ListView.
226 */
227 private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
228
229 /**
230 * Listener for changes in the selected day.
231 */
232 private CalendarView.OnDateChangeListener mOnDateChangeListener;
233
234 /**
235 * Command for adjusting the position after a scroll/fling.
236 */
237 private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
238
239 /**
240 * Temporary instance to avoid multiple instantiations.
241 */
242 private Calendar mTempDate;
243
244 /**
245 * The first day of the focused month.
246 */
247 private Calendar mFirstDayOfMonth;
248
249 /**
250 * The start date of the range supported by this picker.
251 */
252 private Calendar mMinDate;
253
254 /**
255 * The end date of the range supported by this picker.
256 */
257 private Calendar mMaxDate;
258
259 CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs,
260 int defStyleAttr, int defStyleRes) {
261 super(delegator, context);
262
263 final TypedArray a = context.obtainStyledAttributes(attrs,
264 R.styleable.CalendarView, defStyleAttr, defStyleRes);
265 mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber,
266 DEFAULT_SHOW_WEEK_NUMBER);
267 mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
268 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
269 final String minDate = a.getString(R.styleable.CalendarView_minDate);
270 if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
271 parseDate(DEFAULT_MIN_DATE, mMinDate);
272 }
273 final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
274 if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
275 parseDate(DEFAULT_MAX_DATE, mMaxDate);
276 }
277 if (mMaxDate.before(mMinDate)) {
278 throw new IllegalArgumentException("Max date cannot be before min date.");
279 }
280 mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount,
281 DEFAULT_SHOWN_WEEK_COUNT);
282 mSelectedWeekBackgroundColor = a.getColor(
283 R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
284 mFocusedMonthDateColor = a.getColor(
285 R.styleable.CalendarView_focusedMonthDateColor, 0);
286 mUnfocusedMonthDateColor = a.getColor(
287 R.styleable.CalendarView_unfocusedMonthDateColor, 0);
288 mWeekSeparatorLineColor = a.getColor(
289 R.styleable.CalendarView_weekSeparatorLineColor, 0);
290 mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0);
291 mSelectedDateVerticalBar = a.getDrawable(
292 R.styleable.CalendarView_selectedDateVerticalBar);
293
294 mDateTextAppearanceResId = a.getResourceId(
295 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
296 updateDateTextSize();
297
298 mWeekDayTextAppearanceResId = a.getResourceId(
299 R.styleable.CalendarView_weekDayTextAppearance,
300 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
301 a.recycle();
302
303 DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
304 mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
305 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
306 mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
307 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
308 mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
309 UNSCALED_BOTTOM_BUFFER, displayMetrics);
310 mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
311 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
312 mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
313 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
314
315 LayoutInflater layoutInflater = (LayoutInflater) mContext
316 .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
317 View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
318 mDelegator.addView(content);
319
320 mListView = (ListView) mDelegator.findViewById(R.id.list);
321 mDayNamesHeader = (ViewGroup) content.findViewById(R.id.day_names);
322 mMonthName = (TextView) content.findViewById(R.id.month_name);
323
324 setUpHeader();
325 setUpListView();
326 setUpAdapter();
327
328 // go to today or whichever is close to today min or max date
329 mTempDate.setTimeInMillis(System.currentTimeMillis());
330 if (mTempDate.before(mMinDate)) {
331 goTo(mMinDate, false, true, true);
332 } else if (mMaxDate.before(mTempDate)) {
333 goTo(mMaxDate, false, true, true);
334 } else {
335 goTo(mTempDate, false, true, true);
336 }
337
338 mDelegator.invalidate();
339 }
340
341 @Override
342 public void setShownWeekCount(int count) {
343 if (mShownWeekCount != count) {
344 mShownWeekCount = count;
345 mDelegator.invalidate();
346 }
347 }
348
349 @Override
350 public int getShownWeekCount() {
351 return mShownWeekCount;
352 }
353
354 @Override
355 public void setSelectedWeekBackgroundColor(int color) {
356 if (mSelectedWeekBackgroundColor != color) {
357 mSelectedWeekBackgroundColor = color;
358 final int childCount = mListView.getChildCount();
359 for (int i = 0; i < childCount; i++) {
360 WeekView weekView = (WeekView) mListView.getChildAt(i);
361 if (weekView.mHasSelectedDay) {
362 weekView.invalidate();
363 }
364 }
365 }
366 }
367
368 @Override
369 public int getSelectedWeekBackgroundColor() {
370 return mSelectedWeekBackgroundColor;
371 }
372
373 @Override
374 public void setFocusedMonthDateColor(int color) {
375 if (mFocusedMonthDateColor != color) {
376 mFocusedMonthDateColor = color;
377 final int childCount = mListView.getChildCount();
378 for (int i = 0; i < childCount; i++) {
379 WeekView weekView = (WeekView) mListView.getChildAt(i);
380 if (weekView.mHasFocusedDay) {
381 weekView.invalidate();
382 }
383 }
384 }
385 }
386
387 @Override
388 public int getFocusedMonthDateColor() {
389 return mFocusedMonthDateColor;
390 }
391
392 @Override
393 public void setUnfocusedMonthDateColor(int color) {
394 if (mUnfocusedMonthDateColor != color) {
395 mUnfocusedMonthDateColor = color;
396 final int childCount = mListView.getChildCount();
397 for (int i = 0; i < childCount; i++) {
398 WeekView weekView = (WeekView) mListView.getChildAt(i);
399 if (weekView.mHasUnfocusedDay) {
400 weekView.invalidate();
401 }
402 }
403 }
404 }
405
406 @Override
407 public int getUnfocusedMonthDateColor() {
408 return mFocusedMonthDateColor;
409 }
410
411 @Override
412 public void setWeekNumberColor(int color) {
413 if (mWeekNumberColor != color) {
414 mWeekNumberColor = color;
415 if (mShowWeekNumber) {
416 invalidateAllWeekViews();
417 }
418 }
419 }
420
421 @Override
422 public int getWeekNumberColor() {
423 return mWeekNumberColor;
424 }
425
426 @Override
427 public void setWeekSeparatorLineColor(int color) {
428 if (mWeekSeparatorLineColor != color) {
429 mWeekSeparatorLineColor = color;
430 invalidateAllWeekViews();
431 }
432 }
433
434 @Override
435 public int getWeekSeparatorLineColor() {
436 return mWeekSeparatorLineColor;
437 }
438
439 @Override
440 public void setSelectedDateVerticalBar(int resourceId) {
441 Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
442 setSelectedDateVerticalBar(drawable);
443 }
444
445 @Override
446 public void setSelectedDateVerticalBar(Drawable drawable) {
447 if (mSelectedDateVerticalBar != drawable) {
448 mSelectedDateVerticalBar = drawable;
449 final int childCount = mListView.getChildCount();
450 for (int i = 0; i < childCount; i++) {
451 WeekView weekView = (WeekView) mListView.getChildAt(i);
452 if (weekView.mHasSelectedDay) {
453 weekView.invalidate();
454 }
455 }
456 }
457 }
458
459 @Override
460 public Drawable getSelectedDateVerticalBar() {
461 return mSelectedDateVerticalBar;
462 }
463
464 @Override
465 public void setWeekDayTextAppearance(int resourceId) {
466 if (mWeekDayTextAppearanceResId != resourceId) {
467 mWeekDayTextAppearanceResId = resourceId;
468 setUpHeader();
469 }
470 }
471
472 @Override
473 public int getWeekDayTextAppearance() {
474 return mWeekDayTextAppearanceResId;
475 }
476
477 @Override
478 public void setDateTextAppearance(int resourceId) {
479 if (mDateTextAppearanceResId != resourceId) {
480 mDateTextAppearanceResId = resourceId;
481 updateDateTextSize();
482 invalidateAllWeekViews();
483 }
484 }
485
486 @Override
487 public int getDateTextAppearance() {
488 return mDateTextAppearanceResId;
489 }
490
491 @Override
492 public void setMinDate(long minDate) {
493 mTempDate.setTimeInMillis(minDate);
494 if (isSameDate(mTempDate, mMinDate)) {
495 return;
496 }
497 mMinDate.setTimeInMillis(minDate);
498 // make sure the current date is not earlier than
499 // the new min date since the latter is used for
500 // calculating the indices in the adapter thus
501 // avoiding out of bounds error
502 Calendar date = mAdapter.mSelectedDate;
503 if (date.before(mMinDate)) {
504 mAdapter.setSelectedDay(mMinDate);
505 }
506 // reinitialize the adapter since its range depends on min date
507 mAdapter.init();
508 if (date.before(mMinDate)) {
509 setDate(mTempDate.getTimeInMillis());
510 } else {
511 // we go to the current date to force the ListView to query its
512 // adapter for the shown views since we have changed the adapter
513 // range and the base from which the later calculates item indices
514 // note that calling setDate will not work since the date is the same
515 goTo(date, false, true, false);
516 }
517 }
518
519 @Override
520 public long getMinDate() {
521 return mMinDate.getTimeInMillis();
522 }
523
524 @Override
525 public void setMaxDate(long maxDate) {
526 mTempDate.setTimeInMillis(maxDate);
527 if (isSameDate(mTempDate, mMaxDate)) {
528 return;
529 }
530 mMaxDate.setTimeInMillis(maxDate);
531 // reinitialize the adapter since its range depends on max date
532 mAdapter.init();
533 Calendar date = mAdapter.mSelectedDate;
534 if (date.after(mMaxDate)) {
535 setDate(mMaxDate.getTimeInMillis());
536 } else {
537 // we go to the current date to force the ListView to query its
538 // adapter for the shown views since we have changed the adapter
539 // range and the base from which the later calculates item indices
540 // note that calling setDate will not work since the date is the same
541 goTo(date, false, true, false);
542 }
543 }
544
545 @Override
546 public long getMaxDate() {
547 return mMaxDate.getTimeInMillis();
548 }
549
550 @Override
551 public void setShowWeekNumber(boolean showWeekNumber) {
552 if (mShowWeekNumber == showWeekNumber) {
553 return;
554 }
555 mShowWeekNumber = showWeekNumber;
556 mAdapter.notifyDataSetChanged();
557 setUpHeader();
558 }
559
560 @Override
561 public boolean getShowWeekNumber() {
562 return mShowWeekNumber;
563 }
564
565 @Override
566 public void setFirstDayOfWeek(int firstDayOfWeek) {
567 if (mFirstDayOfWeek == firstDayOfWeek) {
568 return;
569 }
570 mFirstDayOfWeek = firstDayOfWeek;
571 mAdapter.init();
572 mAdapter.notifyDataSetChanged();
573 setUpHeader();
574 }
575
576 @Override
577 public int getFirstDayOfWeek() {
578 return mFirstDayOfWeek;
579 }
580
581 @Override
582 public void setDate(long date) {
583 setDate(date, false, false);
584 }
585
586 @Override
587 public void setDate(long date, boolean animate, boolean center) {
588 mTempDate.setTimeInMillis(date);
589 if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
590 return;
591 }
592 goTo(mTempDate, animate, true, center);
593 }
594
595 @Override
596 public long getDate() {
597 return mAdapter.mSelectedDate.getTimeInMillis();
598 }
599
600 @Override
601 public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) {
602 mOnDateChangeListener = listener;
603 }
604
605 @Override
606 public void onConfigurationChanged(Configuration newConfig) {
607 setCurrentLocale(newConfig.locale);
608 }
609
610 /**
611 * Sets the current locale.
612 *
613 * @param locale The current locale.
614 */
615 @Override
616 protected void setCurrentLocale(Locale locale) {
617 super.setCurrentLocale(locale);
618
619 mTempDate = getCalendarForLocale(mTempDate, locale);
620 mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
621 mMinDate = getCalendarForLocale(mMinDate, locale);
622 mMaxDate = getCalendarForLocale(mMaxDate, locale);
623 }
624 private void updateDateTextSize() {
625 TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
626 mDateTextAppearanceResId, R.styleable.TextAppearance);
627 mDateTextSize = dateTextAppearance.getDimensionPixelSize(
628 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
629 dateTextAppearance.recycle();
630 }
631
632 /**
633 * Invalidates all week views.
634 */
635 private void invalidateAllWeekViews() {
636 final int childCount = mListView.getChildCount();
637 for (int i = 0; i < childCount; i++) {
638 View view = mListView.getChildAt(i);
639 view.invalidate();
640 }
641 }
642
643 /**
644 * Gets a calendar for locale bootstrapped with the value of a given calendar.
645 *
646 * @param oldCalendar The old calendar.
647 * @param locale The locale.
648 */
649 private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
650 if (oldCalendar == null) {
651 return Calendar.getInstance(locale);
652 } else {
653 final long currentTimeMillis = oldCalendar.getTimeInMillis();
654 Calendar newCalendar = Calendar.getInstance(locale);
655 newCalendar.setTimeInMillis(currentTimeMillis);
656 return newCalendar;
657 }
658 }
659
660 /**
661 * @return True if the <code>firstDate</code> is the same as the <code>
662 * secondDate</code>.
663 */
664 private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
665 return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
666 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
667 }
668
669 /**
670 * Creates a new adapter if necessary and sets up its parameters.
671 */
672 private void setUpAdapter() {
673 if (mAdapter == null) {
674 mAdapter = new WeeksAdapter(mContext);
675 mAdapter.registerDataSetObserver(new DataSetObserver() {
676 @Override
677 public void onChanged() {
678 if (mOnDateChangeListener != null) {
679 Calendar selectedDay = mAdapter.getSelectedDay();
680 mOnDateChangeListener.onSelectedDayChange(mDelegator,
681 selectedDay.get(Calendar.YEAR),
682 selectedDay.get(Calendar.MONTH),
683 selectedDay.get(Calendar.DAY_OF_MONTH));
684 }
685 }
686 });
687 mListView.setAdapter(mAdapter);
688 }
689
690 // refresh the view with the new parameters
691 mAdapter.notifyDataSetChanged();
692 }
693
694 /**
695 * Sets up the strings to be used by the header.
696 */
697 private void setUpHeader() {
698 mDayNamesShort = new String[mDaysPerWeek];
699 mDayNamesLong = new String[mDaysPerWeek];
700 for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
701 int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
702 mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
703 DateUtils.LENGTH_SHORTEST);
704 mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
705 DateUtils.LENGTH_LONG);
706 }
707
708 TextView label = (TextView) mDayNamesHeader.getChildAt(0);
709 if (mShowWeekNumber) {
710 label.setVisibility(View.VISIBLE);
711 } else {
712 label.setVisibility(View.GONE);
713 }
714 for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
715 label = (TextView) mDayNamesHeader.getChildAt(i);
716 if (mWeekDayTextAppearanceResId > -1) {
717 label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
718 }
719 if (i < mDaysPerWeek + 1) {
720 label.setText(mDayNamesShort[i - 1]);
721 label.setContentDescription(mDayNamesLong[i - 1]);
722 label.setVisibility(View.VISIBLE);
723 } else {
724 label.setVisibility(View.GONE);
725 }
726 }
727 mDayNamesHeader.invalidate();
728 }
729
730 /**
731 * Sets all the required fields for the list view.
732 */
733 private void setUpListView() {
734 // Configure the listview
735 mListView.setDivider(null);
736 mListView.setItemsCanFocus(true);
737 mListView.setVerticalScrollBarEnabled(false);
738 mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
739 public void onScrollStateChanged(AbsListView view, int scrollState) {
740 CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState);
741 }
742
743 public void onScroll(
744 AbsListView view, int firstVisibleItem, int visibleItemCount,
745 int totalItemCount) {
746 CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem,
747 visibleItemCount, totalItemCount);
748 }
749 });
750 // Make the scrolling behavior nicer
751 mListView.setFriction(mFriction);
752 mListView.setVelocityScale(mVelocityScale);
753 }
754
755 /**
756 * This moves to the specified time in the view. If the time is not already
757 * in range it will move the list so that the first of the month containing
758 * the time is at the top of the view. If the new time is already in view
759 * the list will not be scrolled unless forceScroll is true. This time may
760 * optionally be highlighted as selected as well.
761 *
762 * @param date The time to move to.
763 * @param animate Whether to scroll to the given time or just redraw at the
764 * new location.
765 * @param setSelected Whether to set the given time as selected.
766 * @param forceScroll Whether to recenter even if the time is already
767 * visible.
768 *
769 * @throws IllegalArgumentException of the provided date is before the
770 * range start of after the range end.
771 */
772 private void goTo(Calendar date, boolean animate, boolean setSelected,
773 boolean forceScroll) {
774 if (date.before(mMinDate) || date.after(mMaxDate)) {
775 throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
776 + " and " + mMaxDate.getTime());
777 }
778 // Find the first and last entirely visible weeks
779 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
780 View firstChild = mListView.getChildAt(0);
781 if (firstChild != null && firstChild.getTop() < 0) {
782 firstFullyVisiblePosition++;
783 }
784 int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
785 if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
786 lastFullyVisiblePosition--;
787 }
788 if (setSelected) {
789 mAdapter.setSelectedDay(date);
790 }
791 // Get the week we're going to
792 int position = getWeeksSinceMinDate(date);
793
794 // Check if the selected day is now outside of our visible range
795 // and if so scroll to the month that contains it
796 if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
797 || forceScroll) {
798 mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
799 mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
800
801 setMonthDisplayed(mFirstDayOfMonth);
802
803 // the earliest time we can scroll to is the min date
804 if (mFirstDayOfMonth.before(mMinDate)) {
805 position = 0;
806 } else {
807 position = getWeeksSinceMinDate(mFirstDayOfMonth);
808 }
809
810 mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING;
811 if (animate) {
812 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
813 GOTO_SCROLL_DURATION);
814 } else {
815 mListView.setSelectionFromTop(position, mListScrollTopOffset);
816 // Perform any after scroll operations that are needed
817 onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE);
818 }
819 } else if (setSelected) {
820 // Otherwise just set the selection
821 setMonthDisplayed(date);
822 }
823 }
824
825 /**
826 * Called when a <code>view</code> transitions to a new <code>scrollState
827 * </code>.
828 */
829 private void onScrollStateChanged(AbsListView view, int scrollState) {
830 mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
831 }
832
833 /**
834 * Updates the title and selected month if the <code>view</code> has moved to a new
835 * month.
836 */
837 private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
838 int totalItemCount) {
839 WeekView child = (WeekView) view.getChildAt(0);
840 if (child == null) {
841 return;
842 }
843
844 // Figure out where we are
845 long currScroll =
846 view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
847
848 // If we have moved since our last call update the direction
849 if (currScroll < mPreviousScrollPosition) {
850 mIsScrollingUp = true;
851 } else if (currScroll > mPreviousScrollPosition) {
852 mIsScrollingUp = false;
853 } else {
854 return;
855 }
856
857 // Use some hysteresis for checking which month to highlight. This
858 // causes the month to transition when two full weeks of a month are
859 // visible when scrolling up, and when the first day in a month reaches
860 // the top of the screen when scrolling down.
861 int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
862 if (mIsScrollingUp) {
863 child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
864 } else if (offset != 0) {
865 child = (WeekView) view.getChildAt(offset);
866 }
867
868 if (child != null) {
869 // Find out which month we're moving into
870 int month;
871 if (mIsScrollingUp) {
872 month = child.getMonthOfFirstWeekDay();
873 } else {
874 month = child.getMonthOfLastWeekDay();
875 }
876
877 // And how it relates to our current highlighted month
878 int monthDiff;
879 if (mCurrentMonthDisplayed == 11 && month == 0) {
880 monthDiff = 1;
881 } else if (mCurrentMonthDisplayed == 0 && month == 11) {
882 monthDiff = -1;
883 } else {
884 monthDiff = month - mCurrentMonthDisplayed;
885 }
886
887 // Only switch months if we're scrolling away from the currently
888 // selected month
889 if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
890 Calendar firstDay = child.getFirstDay();
891 if (mIsScrollingUp) {
892 firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
893 } else {
894 firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
895 }
896 setMonthDisplayed(firstDay);
897 }
898 }
899 mPreviousScrollPosition = currScroll;
900 mPreviousScrollState = mCurrentScrollState;
901 }
902
903 /**
904 * Sets the month displayed at the top of this view based on time. Override
905 * to add custom events when the title is changed.
906 *
907 * @param calendar A day in the new focus month.
908 */
909 private void setMonthDisplayed(Calendar calendar) {
910 mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
911 mAdapter.setFocusMonth(mCurrentMonthDisplayed);
912 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
913 | DateUtils.FORMAT_SHOW_YEAR;
914 final long millis = calendar.getTimeInMillis();
915 String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
916 mMonthName.setText(newMonthName);
917 mMonthName.invalidate();
918 }
919
920 /**
921 * @return Returns the number of weeks between the current <code>date</code>
922 * and the <code>mMinDate</code>.
923 */
924 private int getWeeksSinceMinDate(Calendar date) {
925 if (date.before(mMinDate)) {
926 throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
927 + " does not precede toDate: " + date.getTime());
928 }
929 long endTimeMillis = date.getTimeInMillis()
930 + date.getTimeZone().getOffset(date.getTimeInMillis());
931 long startTimeMillis = mMinDate.getTimeInMillis()
932 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
933 long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
934 * MILLIS_IN_DAY;
935 return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
936 }
937
938 /**
939 * Command responsible for acting upon scroll state changes.
940 */
941 private class ScrollStateRunnable implements Runnable {
942 private AbsListView mView;
943
944 private int mNewState;
945
946 /**
947 * Sets up the runnable with a short delay in case the scroll state
948 * immediately changes again.
949 *
950 * @param view The list view that changed state
951 * @param scrollState The new state it changed to
952 */
953 public void doScrollStateChange(AbsListView view, int scrollState) {
954 mView = view;
955 mNewState = scrollState;
956 mDelegator.removeCallbacks(this);
957 mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
958 }
959
960 public void run() {
961 mCurrentScrollState = mNewState;
962 // Fix the position after a scroll or a fling ends
963 if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
964 && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
965 View child = mView.getChildAt(0);
966 if (child == null) {
967 // The view is no longer visible, just return
968 return;
969 }
970 int dist = child.getBottom() - mListScrollTopOffset;
971 if (dist > mListScrollTopOffset) {
972 if (mIsScrollingUp) {
973 mView.smoothScrollBy(dist - child.getHeight(),
974 ADJUSTMENT_SCROLL_DURATION);
975 } else {
976 mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
977 }
978 }
979 }
980 mPreviousScrollState = mNewState;
981 }
982 }
983
984 /**
985 * <p>
986 * This is a specialized adapter for creating a list of weeks with
987 * selectable days. It can be configured to display the week number, start
988 * the week on a given day, show a reduced number of days, or display an
989 * arbitrary number of weeks at a time.
990 * </p>
991 */
992 private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener {
993
994 private int mSelectedWeek;
995
996 private GestureDetector mGestureDetector;
997
998 private int mFocusedMonth;
999
1000 private final Calendar mSelectedDate = Calendar.getInstance();
1001
1002 private int mTotalWeekCount;
1003
1004 public WeeksAdapter(Context context) {
1005 mContext = context;
1006 mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener());
1007 init();
1008 }
1009
1010 /**
1011 * Set up the gesture detector and selected time
1012 */
1013 private void init() {
1014 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1015 mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
1016 if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
1017 || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
1018 mTotalWeekCount++;
1019 }
1020 notifyDataSetChanged();
1021 }
1022
1023 /**
1024 * Updates the selected day and related parameters.
1025 *
1026 * @param selectedDay The time to highlight
1027 */
1028 public void setSelectedDay(Calendar selectedDay) {
1029 if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
1030 && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
1031 return;
1032 }
1033 mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
1034 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1035 mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
1036 notifyDataSetChanged();
1037 }
1038
1039 /**
1040 * @return The selected day of month.
1041 */
1042 public Calendar getSelectedDay() {
1043 return mSelectedDate;
1044 }
1045
1046 @Override
1047 public int getCount() {
1048 return mTotalWeekCount;
1049 }
1050
1051 @Override
1052 public Object getItem(int position) {
1053 return null;
1054 }
1055
1056 @Override
1057 public long getItemId(int position) {
1058 return position;
1059 }
1060
1061 @Override
1062 public View getView(int position, View convertView, ViewGroup parent) {
1063 WeekView weekView = null;
1064 if (convertView != null) {
1065 weekView = (WeekView) convertView;
1066 } else {
1067 weekView = new WeekView(mContext);
1068 AbsListView.LayoutParams params =
1069 new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
1070 FrameLayout.LayoutParams.WRAP_CONTENT);
1071 weekView.setLayoutParams(params);
1072 weekView.setClickable(true);
1073 weekView.setOnTouchListener(this);
1074 }
1075
1076 int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1077 Calendar.DAY_OF_WEEK) : -1;
1078 weekView.init(position, selectedWeekDay, mFocusedMonth);
1079
1080 return weekView;
1081 }
1082
1083 /**
1084 * Changes which month is in focus and updates the view.
1085 *
1086 * @param month The month to show as in focus [0-11]
1087 */
1088 public void setFocusMonth(int month) {
1089 if (mFocusedMonth == month) {
1090 return;
1091 }
1092 mFocusedMonth = month;
1093 notifyDataSetChanged();
1094 }
1095
1096 @Override
1097 public boolean onTouch(View v, MotionEvent event) {
1098 if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
1099 WeekView weekView = (WeekView) v;
1100 // if we cannot find a day for the given location we are done
1101 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
1102 return true;
1103 }
1104 // it is possible that the touched day is outside the valid range
1105 // we draw whole weeks but range end can fall not on the week end
1106 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1107 return true;
1108 }
1109 onDateTapped(mTempDate);
1110 return true;
1111 }
1112 return false;
1113 }
1114
1115 /**
1116 * Maintains the same hour/min/sec but moves the day to the tapped day.
1117 *
1118 * @param day The day that was tapped
1119 */
1120 private void onDateTapped(Calendar day) {
1121 setSelectedDay(day);
1122 setMonthDisplayed(day);
1123 }
1124
1125 /**
1126 * This is here so we can identify single tap events and set the
1127 * selected day correctly
1128 */
1129 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
1130 @Override
1131 public boolean onSingleTapUp(MotionEvent e) {
1132 return true;
1133 }
1134 }
1135 }
1136
1137 /**
1138 * <p>
1139 * This is a dynamic view for drawing a single week. It can be configured to
1140 * display the week number, start the week on a given day, or show a reduced
1141 * number of days. It is intended for use as a single view within a
1142 * ListView. See {@link WeeksAdapter} for usage.
1143 * </p>
1144 */
1145 private class WeekView extends View {
1146
1147 private final Rect mTempRect = new Rect();
1148
1149 private final Paint mDrawPaint = new Paint();
1150
1151 private final Paint mMonthNumDrawPaint = new Paint();
1152
1153 // Cache the number strings so we don't have to recompute them each time
1154 private String[] mDayNumbers;
1155
1156 // Quick lookup for checking which days are in the focus month
1157 private boolean[] mFocusDay;
1158
1159 // Whether this view has a focused day.
1160 private boolean mHasFocusedDay;
1161
1162 // Whether this view has only focused days.
1163 private boolean mHasUnfocusedDay;
1164
1165 // The first day displayed by this item
1166 private Calendar mFirstDay;
1167
1168 // The month of the first day in this week
1169 private int mMonthOfFirstWeekDay = -1;
1170
1171 // The month of the last day in this week
1172 private int mLastWeekDayMonth = -1;
1173
1174 // The position of this week, equivalent to weeks since the week of Jan
1175 // 1st, 1900
1176 private int mWeek = -1;
1177
1178 // Quick reference to the width of this view, matches parent
1179 private int mWidth;
1180
1181 // The height this view should draw at in pixels, set by height param
1182 private int mHeight;
1183
1184 // If this view contains the selected day
1185 private boolean mHasSelectedDay = false;
1186
1187 // Which day is selected [0-6] or -1 if no day is selected
1188 private int mSelectedDay = -1;
1189
1190 // The number of days + a spot for week number if it is displayed
1191 private int mNumCells;
1192
1193 // The left edge of the selected day
1194 private int mSelectedLeft = -1;
1195
1196 // The right edge of the selected day
1197 private int mSelectedRight = -1;
1198
1199 public WeekView(Context context) {
1200 super(context);
1201
1202 // Sets up any standard paints that will be used
1203 initilaizePaints();
1204 }
1205
1206 /**
1207 * Initializes this week view.
1208 *
1209 * @param weekNumber The number of the week this view represents. The
1210 * week number is a zero based index of the weeks since
1211 * {@link android.widget.CalendarView#getMinDate()}.
1212 * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
1213 * selected day.
1214 * @param focusedMonth The month that is currently in focus i.e.
1215 * highlighted.
1216 */
1217 public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
1218 mSelectedDay = selectedWeekDay;
1219 mHasSelectedDay = mSelectedDay != -1;
1220 mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
1221 mWeek = weekNumber;
1222 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
1223
1224 mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1225 mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
1226
1227 // Allocate space for caching the day numbers and focus values
1228 mDayNumbers = new String[mNumCells];
1229 mFocusDay = new boolean[mNumCells];
1230
1231 // If we're showing the week number calculate it based on Monday
1232 int i = 0;
1233 if (mShowWeekNumber) {
1234 mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1235 mTempDate.get(Calendar.WEEK_OF_YEAR));
1236 i++;
1237 }
1238
1239 // Now adjust our starting day based on the start day of the week
1240 int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
1241 mTempDate.add(Calendar.DAY_OF_MONTH, diff);
1242
1243 mFirstDay = (Calendar) mTempDate.clone();
1244 mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
1245
1246 mHasUnfocusedDay = true;
1247 for (; i < mNumCells; i++) {
1248 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
1249 mFocusDay[i] = isFocusedDay;
1250 mHasFocusedDay |= isFocusedDay;
1251 mHasUnfocusedDay &= !isFocusedDay;
1252 // do not draw dates outside the valid range to avoid user confusion
1253 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1254 mDayNumbers[i] = "";
1255 } else {
1256 mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
1257 mTempDate.get(Calendar.DAY_OF_MONTH));
1258 }
1259 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
1260 }
1261 // We do one extra add at the end of the loop, if that pushed us to
1262 // new month undo it
1263 if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
1264 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
1265 }
1266 mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
1267
1268 updateSelectionPositions();
1269 }
1270
1271 /**
1272 * Initialize the paint instances.
1273 */
1274 private void initilaizePaints() {
1275 mDrawPaint.setFakeBoldText(false);
1276 mDrawPaint.setAntiAlias(true);
1277 mDrawPaint.setStyle(Paint.Style.FILL);
1278
1279 mMonthNumDrawPaint.setFakeBoldText(true);
1280 mMonthNumDrawPaint.setAntiAlias(true);
1281 mMonthNumDrawPaint.setStyle(Paint.Style.FILL);
1282 mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER);
1283 mMonthNumDrawPaint.setTextSize(mDateTextSize);
1284 }
1285
1286 /**
1287 * Returns the month of the first day in this week.
1288 *
1289 * @return The month the first day of this view is in.
1290 */
1291 public int getMonthOfFirstWeekDay() {
1292 return mMonthOfFirstWeekDay;
1293 }
1294
1295 /**
1296 * Returns the month of the last day in this week
1297 *
1298 * @return The month the last day of this view is in
1299 */
1300 public int getMonthOfLastWeekDay() {
1301 return mLastWeekDayMonth;
1302 }
1303
1304 /**
1305 * Returns the first day in this view.
1306 *
1307 * @return The first day in the view.
1308 */
1309 public Calendar getFirstDay() {
1310 return mFirstDay;
1311 }
1312
1313 /**
1314 * Calculates the day that the given x position is in, accounting for
1315 * week number.
1316 *
1317 * @param x The x position of the touch event.
1318 * @return True if a day was found for the given location.
1319 */
1320 public boolean getDayFromLocation(float x, Calendar outCalendar) {
1321 final boolean isLayoutRtl = isLayoutRtl();
1322
1323 int start;
1324 int end;
1325
1326 if (isLayoutRtl) {
1327 start = 0;
1328 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1329 } else {
1330 start = mShowWeekNumber ? mWidth / mNumCells : 0;
1331 end = mWidth;
1332 }
1333
1334 if (x < start || x > end) {
1335 outCalendar.clear();
1336 return false;
1337 }
1338
1339 // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
1340 int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
1341
1342 if (isLayoutRtl) {
1343 dayPosition = mDaysPerWeek - 1 - dayPosition;
1344 }
1345
1346 outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
1347 outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
1348
1349 return true;
1350 }
1351
1352 @Override
1353 protected void onDraw(Canvas canvas) {
1354 drawBackground(canvas);
1355 drawWeekNumbersAndDates(canvas);
1356 drawWeekSeparators(canvas);
1357 drawSelectedDateVerticalBars(canvas);
1358 }
1359
1360 /**
1361 * This draws the selection highlight if a day is selected in this week.
1362 *
1363 * @param canvas The canvas to draw on
1364 */
1365 private void drawBackground(Canvas canvas) {
1366 if (!mHasSelectedDay) {
1367 return;
1368 }
1369 mDrawPaint.setColor(mSelectedWeekBackgroundColor);
1370
1371 mTempRect.top = mWeekSeperatorLineWidth;
1372 mTempRect.bottom = mHeight;
1373
1374 final boolean isLayoutRtl = isLayoutRtl();
1375
1376 if (isLayoutRtl) {
1377 mTempRect.left = 0;
1378 mTempRect.right = mSelectedLeft - 2;
1379 } else {
1380 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
1381 mTempRect.right = mSelectedLeft - 2;
1382 }
1383 canvas.drawRect(mTempRect, mDrawPaint);
1384
1385 if (isLayoutRtl) {
1386 mTempRect.left = mSelectedRight + 3;
1387 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1388 } else {
1389 mTempRect.left = mSelectedRight + 3;
1390 mTempRect.right = mWidth;
1391 }
1392 canvas.drawRect(mTempRect, mDrawPaint);
1393 }
1394
1395 /**
1396 * Draws the week and month day numbers for this week.
1397 *
1398 * @param canvas The canvas to draw on
1399 */
1400 private void drawWeekNumbersAndDates(Canvas canvas) {
1401 final float textHeight = mDrawPaint.getTextSize();
1402 final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
1403 final int nDays = mNumCells;
1404 final int divisor = 2 * nDays;
1405
1406 mDrawPaint.setTextAlign(Paint.Align.CENTER);
1407 mDrawPaint.setTextSize(mDateTextSize);
1408
1409 int i = 0;
1410
1411 if (isLayoutRtl()) {
1412 for (; i < nDays - 1; i++) {
1413 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
1414 : mUnfocusedMonthDateColor);
1415 int x = (2 * i + 1) * mWidth / divisor;
1416 canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
1417 }
1418 if (mShowWeekNumber) {
1419 mDrawPaint.setColor(mWeekNumberColor);
1420 int x = mWidth - mWidth / divisor;
1421 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
1422 }
1423 } else {
1424 if (mShowWeekNumber) {
1425 mDrawPaint.setColor(mWeekNumberColor);
1426 int x = mWidth / divisor;
1427 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
1428 i++;
1429 }
1430 for (; i < nDays; i++) {
1431 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
1432 : mUnfocusedMonthDateColor);
1433 int x = (2 * i + 1) * mWidth / divisor;
1434 canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
1435 }
1436 }
1437 }
1438
1439 /**
1440 * Draws a horizontal line for separating the weeks.
1441 *
1442 * @param canvas The canvas to draw on.
1443 */
1444 private void drawWeekSeparators(Canvas canvas) {
1445 // If it is the topmost fully visible child do not draw separator line
1446 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
1447 if (mListView.getChildAt(0).getTop() < 0) {
1448 firstFullyVisiblePosition++;
1449 }
1450 if (firstFullyVisiblePosition == mWeek) {
1451 return;
1452 }
1453 mDrawPaint.setColor(mWeekSeparatorLineColor);
1454 mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
1455 float startX;
1456 float stopX;
1457 if (isLayoutRtl()) {
1458 startX = 0;
1459 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1460 } else {
1461 startX = mShowWeekNumber ? mWidth / mNumCells : 0;
1462 stopX = mWidth;
1463 }
1464 canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
1465 }
1466
1467 /**
1468 * Draws the selected date bars if this week has a selected day.
1469 *
1470 * @param canvas The canvas to draw on
1471 */
1472 private void drawSelectedDateVerticalBars(Canvas canvas) {
1473 if (!mHasSelectedDay) {
1474 return;
1475 }
1476 mSelectedDateVerticalBar.setBounds(
1477 mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
1478 mWeekSeperatorLineWidth,
1479 mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
1480 mHeight);
1481 mSelectedDateVerticalBar.draw(canvas);
1482 mSelectedDateVerticalBar.setBounds(
1483 mSelectedRight - mSelectedDateVerticalBarWidth / 2,
1484 mWeekSeperatorLineWidth,
1485 mSelectedRight + mSelectedDateVerticalBarWidth / 2,
1486 mHeight);
1487 mSelectedDateVerticalBar.draw(canvas);
1488 }
1489
1490 @Override
1491 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1492 mWidth = w;
1493 updateSelectionPositions();
1494 }
1495
1496 /**
1497 * This calculates the positions for the selected day lines.
1498 */
1499 private void updateSelectionPositions() {
1500 if (mHasSelectedDay) {
1501 final boolean isLayoutRtl = isLayoutRtl();
1502 int selectedPosition = mSelectedDay - mFirstDayOfWeek;
1503 if (selectedPosition < 0) {
1504 selectedPosition += 7;
1505 }
1506 if (mShowWeekNumber && !isLayoutRtl) {
1507 selectedPosition++;
1508 }
1509 if (isLayoutRtl) {
1510 mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
1511
1512 } else {
1513 mSelectedLeft = selectedPosition * mWidth / mNumCells;
1514 }
1515 mSelectedRight = mSelectedLeft + mWidth / mNumCells;
1516 }
1517 }
1518
1519 @Override
1520 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1521 mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
1522 .getPaddingBottom()) / mShownWeekCount;
1523 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
1524 }
1525 }
1526
1527}