blob: 0957ab4670e613484377b95f2a579edaa69b48c5 [file] [log] [blame]
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001/*
2 * Copyright (C) 2010 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
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080019import android.annotation.Widget;
20import android.app.Service;
21import android.content.Context;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070022import android.content.res.Configuration;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080023import android.content.res.TypedArray;
24import android.database.DataSetObserver;
25import android.graphics.Canvas;
26import android.graphics.Paint;
27import android.graphics.Paint.Align;
28import android.graphics.Paint.Style;
29import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.text.TextUtils;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080032import android.text.format.DateUtils;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080033import android.util.AttributeSet;
34import android.util.DisplayMetrics;
35import android.util.Log;
36import android.util.TypedValue;
37import android.view.GestureDetector;
38import android.view.LayoutInflater;
39import android.view.MotionEvent;
40import android.view.View;
41import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080042import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080044import android.widget.AbsListView.OnScrollListener;
45
Svetoslav Ganov1442da62011-10-31 18:55:15 -070046import com.android.internal.R;
47
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080048import java.text.ParseException;
49import java.text.SimpleDateFormat;
50import java.util.Calendar;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080051import java.util.Locale;
52import java.util.TimeZone;
53
54import libcore.icu.LocaleData;
55
56/**
57 * This class is a calendar widget for displaying and selecting dates. The range
58 * of dates supported by this calendar is configurable. A user can select a date
59 * by taping on it and can scroll and fling the calendar to a desired date.
60 *
61 * @attr ref android.R.styleable#CalendarView_showWeekNumber
62 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
63 * @attr ref android.R.styleable#CalendarView_minDate
64 * @attr ref android.R.styleable#CalendarView_maxDate
Svetoslav Ganove51f94c2010-12-28 15:21:43 -080065 * @attr ref android.R.styleable#CalendarView_shownWeekCount
66 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
67 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
68 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
69 * @attr ref android.R.styleable#CalendarView_weekNumberColor
70 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
71 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
72 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
73 * @attr ref android.R.styleable#CalendarView_dateTextAppearance
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080074 */
75@Widget
76public class CalendarView extends FrameLayout {
77
78 /**
79 * Tag for logging.
80 */
81 private static final String LOG_TAG = CalendarView.class.getSimpleName();
82
83 /**
84 * Default value whether to show week number.
85 */
86 private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
87
88 /**
89 * The number of milliseconds in a day.e
90 */
91 private static final long MILLIS_IN_DAY = 86400000L;
92
93 /**
94 * The number of day in a week.
95 */
96 private static final int DAYS_PER_WEEK = 7;
97
98 /**
99 * The number of milliseconds in a week.
100 */
101 private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
102
103 /**
104 * Affects when the month selection will change while scrolling upe
105 */
106 private static final int SCROLL_HYST_WEEKS = 2;
107
108 /**
109 * How long the GoTo fling animation should last.
110 */
111 private static final int GOTO_SCROLL_DURATION = 1000;
112
113 /**
114 * The duration of the adjustment upon a user scroll in milliseconds.
115 */
116 private static final int ADJUSTMENT_SCROLL_DURATION = 500;
117
118 /**
119 * How long to wait after receiving an onScrollStateChanged notification
120 * before acting on it.
121 */
122 private static final int SCROLL_CHANGE_DELAY = 40;
123
124 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800125 * String for parsing dates.
126 */
127 private static final String DATE_FORMAT = "MM/dd/yyyy";
128
129 /**
130 * The default minimal date.
131 */
132 private static final String DEFAULT_MIN_DATE = "01/01/1900";
133
134 /**
135 * The default maximal date.
136 */
137 private static final String DEFAULT_MAX_DATE = "01/01/2100";
138
139 private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
140
141 private static final int DEFAULT_DATE_TEXT_SIZE = 14;
142
143 private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
144
145 private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
146
147 private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
148
149 private static final int UNSCALED_BOTTOM_BUFFER = 20;
150
151 private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
152
153 private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
154
155 private final int mWeekSeperatorLineWidth;
156
Svetoslav Ganovff375052012-03-01 15:57:22 -0800157 private int mDateTextSize;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800158
Svetoslav Ganovff375052012-03-01 15:57:22 -0800159 private Drawable mSelectedDateVerticalBar;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800160
161 private final int mSelectedDateVerticalBarWidth;
162
Svetoslav Ganovff375052012-03-01 15:57:22 -0800163 private int mSelectedWeekBackgroundColor;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800164
Svetoslav Ganovff375052012-03-01 15:57:22 -0800165 private int mFocusedMonthDateColor;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800166
Svetoslav Ganovff375052012-03-01 15:57:22 -0800167 private int mUnfocusedMonthDateColor;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800168
Svetoslav Ganovff375052012-03-01 15:57:22 -0800169 private int mWeekSeparatorLineColor;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800170
Svetoslav Ganovff375052012-03-01 15:57:22 -0800171 private int mWeekNumberColor;
172
173 private int mWeekDayTextAppearanceResId;
174
175 private int mDateTextAppearanceResId;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800176
177 /**
178 * The top offset of the weeks list.
179 */
180 private int mListScrollTopOffset = 2;
181
182 /**
183 * The visible height of a week view.
184 */
185 private int mWeekMinVisibleHeight = 12;
186
187 /**
188 * The visible height of a week view.
189 */
190 private int mBottomBuffer = 20;
191
192 /**
193 * The number of shown weeks.
194 */
195 private int mShownWeekCount;
196
197 /**
198 * Flag whether to show the week number.
199 */
200 private boolean mShowWeekNumber;
201
202 /**
203 * The number of day per week to be shown.
204 */
205 private int mDaysPerWeek = 7;
206
207 /**
208 * The friction of the week list while flinging.
209 */
210 private float mFriction = .05f;
211
212 /**
213 * Scale for adjusting velocity of the week list while flinging.
214 */
215 private float mVelocityScale = 0.333f;
216
217 /**
218 * The adapter for the weeks list.
219 */
220 private WeeksAdapter mAdapter;
221
222 /**
223 * The weeks list.
224 */
225 private ListView mListView;
226
227 /**
228 * The name of the month to display.
229 */
230 private TextView mMonthName;
231
232 /**
233 * The header with week day names.
234 */
235 private ViewGroup mDayNamesHeader;
236
237 /**
238 * Cached labels for the week names header.
239 */
240 private String[] mDayLabels;
241
242 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800243 * The first day of the week.
244 */
245 private int mFirstDayOfWeek;
246
247 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800248 * Which month should be displayed/highlighted [0-11].
249 */
Ki-Hwan Leef537c9b2013-03-21 13:45:56 +0900250 private int mCurrentMonthDisplayed = -1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800251
252 /**
253 * Used for tracking during a scroll.
254 */
255 private long mPreviousScrollPosition;
256
257 /**
258 * Used for tracking which direction the view is scrolling.
259 */
260 private boolean mIsScrollingUp = false;
261
262 /**
263 * The previous scroll state of the weeks ListView.
264 */
265 private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
266
267 /**
268 * The current scroll state of the weeks ListView.
269 */
270 private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
271
272 /**
273 * Listener for changes in the selected day.
274 */
275 private OnDateChangeListener mOnDateChangeListener;
276
277 /**
278 * Command for adjusting the position after a scroll/fling.
279 */
280 private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
281
282 /**
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700283 * Temporary instance to avoid multiple instantiations.
284 */
285 private Calendar mTempDate;
286
287 /**
288 * The first day of the focused month.
289 */
290 private Calendar mFirstDayOfMonth;
291
292 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800293 * The start date of the range supported by this picker.
294 */
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700295 private Calendar mMinDate;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800296
297 /**
298 * The end date of the range supported by this picker.
299 */
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700300 private Calendar mMaxDate;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800301
302 /**
303 * Date format for parsing dates.
304 */
305 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
306
307 /**
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700308 * The current locale.
309 */
310 private Locale mCurrentLocale;
311
312 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800313 * The callback used to indicate the user changes the date.
314 */
315 public interface OnDateChangeListener {
316
317 /**
318 * Called upon change of the selected day.
319 *
320 * @param view The view associated with this listener.
321 * @param year The year that was set.
322 * @param month The month that was set [0-11].
323 * @param dayOfMonth The day of the month that was set.
324 */
325 public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth);
326 }
327
328 public CalendarView(Context context) {
329 this(context, null);
330 }
331
332 public CalendarView(Context context, AttributeSet attrs) {
333 this(context, attrs, 0);
334 }
335
336 public CalendarView(Context context, AttributeSet attrs, int defStyle) {
337 super(context, attrs, 0);
338
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700339 // initialization based on locale
340 setCurrentLocale(Locale.getDefault());
341
Svetoslav Ganovf1189e92011-09-14 20:18:08 -0700342 TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView,
343 R.attr.calendarViewStyle, 0);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800344 mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
345 DEFAULT_SHOW_WEEK_NUMBER);
346 mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
347 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
348 String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
349 if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
350 parseDate(DEFAULT_MIN_DATE, mMinDate);
351 }
352 String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
353 if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
354 parseDate(DEFAULT_MAX_DATE, mMaxDate);
355 }
Svetoslav Ganovf1189e92011-09-14 20:18:08 -0700356 if (mMaxDate.before(mMinDate)) {
357 throw new IllegalArgumentException("Max date cannot be before min date.");
358 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800359 mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
360 DEFAULT_SHOWN_WEEK_COUNT);
361 mSelectedWeekBackgroundColor = attributesArray.getColor(
362 R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
363 mFocusedMonthDateColor = attributesArray.getColor(
364 R.styleable.CalendarView_focusedMonthDateColor, 0);
365 mUnfocusedMonthDateColor = attributesArray.getColor(
366 R.styleable.CalendarView_unfocusedMonthDateColor, 0);
367 mWeekSeparatorLineColor = attributesArray.getColor(
368 R.styleable.CalendarView_weekSeparatorLineColor, 0);
369 mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
370 mSelectedDateVerticalBar = attributesArray.getDrawable(
371 R.styleable.CalendarView_selectedDateVerticalBar);
372
Svetoslav Ganovff375052012-03-01 15:57:22 -0800373 mDateTextAppearanceResId = attributesArray.getResourceId(
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800374 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800375 updateDateTextSize();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800376
Svetoslav Ganovff375052012-03-01 15:57:22 -0800377 mWeekDayTextAppearanceResId = attributesArray.getResourceId(
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800378 R.styleable.CalendarView_weekDayTextAppearance,
379 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
380 attributesArray.recycle();
381
382 DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
383 mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
384 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
385 mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
386 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
387 mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
388 UNSCALED_BOTTOM_BUFFER, displayMetrics);
389 mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
390 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
391 mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
392 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
393
Alan Viverette22c1c2c2013-09-17 14:39:07 -0700394 LayoutInflater layoutInflater = (LayoutInflater) context
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800395 .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
396 View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
397 addView(content);
398
399 mListView = (ListView) findViewById(R.id.list);
400 mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
401 mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
402
Svetoslav Ganovff375052012-03-01 15:57:22 -0800403 setUpHeader();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800404 setUpListView();
405 setUpAdapter();
406
Svetoslav Ganovf1189e92011-09-14 20:18:08 -0700407 // go to today or whichever is close to today min or max date
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800408 mTempDate.setTimeInMillis(System.currentTimeMillis());
Svetoslav Ganovf1189e92011-09-14 20:18:08 -0700409 if (mTempDate.before(mMinDate)) {
410 goTo(mMinDate, false, true, true);
411 } else if (mMaxDate.before(mTempDate)) {
412 goTo(mMaxDate, false, true, true);
413 } else {
414 goTo(mTempDate, false, true, true);
415 }
416
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800417 invalidate();
418 }
419
Svetoslav Ganovff375052012-03-01 15:57:22 -0800420 /**
421 * Sets the number of weeks to be shown.
422 *
423 * @param count The shown week count.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700424 *
425 * @attr ref android.R.styleable#CalendarView_shownWeekCount
Svetoslav Ganovff375052012-03-01 15:57:22 -0800426 */
427 public void setShownWeekCount(int count) {
428 if (mShownWeekCount != count) {
429 mShownWeekCount = count;
430 invalidate();
431 }
432 }
433
434 /**
435 * Gets the number of weeks to be shown.
436 *
437 * @return The shown week count.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700438 *
439 * @attr ref android.R.styleable#CalendarView_shownWeekCount
Svetoslav Ganovff375052012-03-01 15:57:22 -0800440 */
441 public int getShownWeekCount() {
442 return mShownWeekCount;
443 }
444
445 /**
446 * Sets the background color for the selected week.
447 *
448 * @param color The week background color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700449 *
450 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800451 */
452 public void setSelectedWeekBackgroundColor(int color) {
453 if (mSelectedWeekBackgroundColor != color) {
454 mSelectedWeekBackgroundColor = color;
455 final int childCount = mListView.getChildCount();
456 for (int i = 0; i < childCount; i++) {
457 WeekView weekView = (WeekView) mListView.getChildAt(i);
458 if (weekView.mHasSelectedDay) {
459 weekView.invalidate();
460 }
461 }
462 }
463 }
464
465 /**
466 * Gets the background color for the selected week.
467 *
468 * @return The week background color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700469 *
470 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800471 */
472 public int getSelectedWeekBackgroundColor() {
473 return mSelectedWeekBackgroundColor;
474 }
475
476 /**
477 * Sets the color for the dates of the focused month.
478 *
479 * @param color The focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700480 *
481 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800482 */
483 public void setFocusedMonthDateColor(int color) {
484 if (mFocusedMonthDateColor != color) {
485 mFocusedMonthDateColor = color;
486 final int childCount = mListView.getChildCount();
487 for (int i = 0; i < childCount; i++) {
488 WeekView weekView = (WeekView) mListView.getChildAt(i);
489 if (weekView.mHasFocusedDay) {
490 weekView.invalidate();
491 }
492 }
493 }
494 }
495
496 /**
497 * Gets the color for the dates in the focused month.
498 *
499 * @return The focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700500 *
501 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800502 */
503 public int getFocusedMonthDateColor() {
504 return mFocusedMonthDateColor;
505 }
506
507 /**
508 * Sets the color for the dates of a not focused month.
509 *
510 * @param color A not focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700511 *
512 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800513 */
514 public void setUnfocusedMonthDateColor(int color) {
515 if (mUnfocusedMonthDateColor != color) {
516 mUnfocusedMonthDateColor = color;
517 final int childCount = mListView.getChildCount();
518 for (int i = 0; i < childCount; i++) {
519 WeekView weekView = (WeekView) mListView.getChildAt(i);
520 if (weekView.mHasUnfocusedDay) {
521 weekView.invalidate();
522 }
523 }
524 }
525 }
526
527 /**
528 * Gets the color for the dates in a not focused month.
529 *
530 * @return A not focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700531 *
532 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800533 */
534 public int getUnfocusedMonthDateColor() {
535 return mFocusedMonthDateColor;
536 }
537
538 /**
539 * Sets the color for the week numbers.
540 *
541 * @param color The week number color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700542 *
543 * @attr ref android.R.styleable#CalendarView_weekNumberColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800544 */
545 public void setWeekNumberColor(int color) {
546 if (mWeekNumberColor != color) {
547 mWeekNumberColor = color;
548 if (mShowWeekNumber) {
549 invalidateAllWeekViews();
550 }
551 }
552 }
553
554 /**
555 * Gets the color for the week numbers.
556 *
557 * @return The week number color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700558 *
559 * @attr ref android.R.styleable#CalendarView_weekNumberColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800560 */
561 public int getWeekNumberColor() {
562 return mWeekNumberColor;
563 }
564
565 /**
566 * Sets the color for the separator line between weeks.
567 *
568 * @param color The week separator color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700569 *
570 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800571 */
572 public void setWeekSeparatorLineColor(int color) {
573 if (mWeekSeparatorLineColor != color) {
574 mWeekSeparatorLineColor = color;
575 invalidateAllWeekViews();
576 }
577 }
578
579 /**
580 * Gets the color for the separator line between weeks.
581 *
582 * @return The week separator color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700583 *
584 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800585 */
586 public int getWeekSeparatorLineColor() {
587 return mWeekSeparatorLineColor;
588 }
589
590 /**
591 * Sets the drawable for the vertical bar shown at the beginning and at
592 * the end of the selected date.
593 *
594 * @param resourceId The vertical bar drawable resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700595 *
596 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
Svetoslav Ganovff375052012-03-01 15:57:22 -0800597 */
598 public void setSelectedDateVerticalBar(int resourceId) {
599 Drawable drawable = getResources().getDrawable(resourceId);
600 setSelectedDateVerticalBar(drawable);
601 }
602
603 /**
604 * Sets the drawable for the vertical bar shown at the beginning and at
605 * the end of the selected date.
606 *
607 * @param drawable The vertical bar drawable.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700608 *
609 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
Svetoslav Ganovff375052012-03-01 15:57:22 -0800610 */
611 public void setSelectedDateVerticalBar(Drawable drawable) {
612 if (mSelectedDateVerticalBar != drawable) {
613 mSelectedDateVerticalBar = drawable;
614 final int childCount = mListView.getChildCount();
615 for (int i = 0; i < childCount; i++) {
616 WeekView weekView = (WeekView) mListView.getChildAt(i);
617 if (weekView.mHasSelectedDay) {
618 weekView.invalidate();
619 }
620 }
621 }
622 }
623
624 /**
625 * Gets the drawable for the vertical bar shown at the beginning and at
626 * the end of the selected date.
627 *
628 * @return The vertical bar drawable.
629 */
630 public Drawable getSelectedDateVerticalBar() {
631 return mSelectedDateVerticalBar;
632 }
633
634 /**
635 * Sets the text appearance for the week day abbreviation of the calendar header.
636 *
637 * @param resourceId The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700638 *
639 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800640 */
641 public void setWeekDayTextAppearance(int resourceId) {
642 if (mWeekDayTextAppearanceResId != resourceId) {
643 mWeekDayTextAppearanceResId = resourceId;
644 setUpHeader();
645 }
646 }
647
648 /**
649 * Gets the text appearance for the week day abbreviation of the calendar header.
650 *
651 * @return The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700652 *
653 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800654 */
655 public int getWeekDayTextAppearance() {
656 return mWeekDayTextAppearanceResId;
657 }
658
659 /**
660 * Sets the text appearance for the calendar dates.
661 *
662 * @param resourceId The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700663 *
664 * @attr ref android.R.styleable#CalendarView_dateTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800665 */
666 public void setDateTextAppearance(int resourceId) {
667 if (mDateTextAppearanceResId != resourceId) {
668 mDateTextAppearanceResId = resourceId;
669 updateDateTextSize();
670 invalidateAllWeekViews();
671 }
672 }
673
674 /**
675 * Gets the text appearance for the calendar dates.
676 *
677 * @return The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700678 *
679 * @attr ref android.R.styleable#CalendarView_dateTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800680 */
681 public int getDateTextAppearance() {
682 return mDateTextAppearanceResId;
683 }
684
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800685 @Override
686 public void setEnabled(boolean enabled) {
687 mListView.setEnabled(enabled);
688 }
689
690 @Override
691 public boolean isEnabled() {
692 return mListView.isEnabled();
693 }
694
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700695 @Override
696 protected void onConfigurationChanged(Configuration newConfig) {
697 super.onConfigurationChanged(newConfig);
698 setCurrentLocale(newConfig.locale);
699 }
700
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800701 @Override
702 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
703 super.onInitializeAccessibilityEvent(event);
704 event.setClassName(CalendarView.class.getName());
705 }
706
707 @Override
708 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
709 super.onInitializeAccessibilityNodeInfo(info);
710 info.setClassName(CalendarView.class.getName());
711 }
712
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800713 /**
714 * Gets the minimal date supported by this {@link CalendarView} in milliseconds
715 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
716 * zone.
717 * <p>
718 * Note: The default minimal date is 01/01/1900.
719 * <p>
720 *
721 * @return The minimal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700722 *
723 * @attr ref android.R.styleable#CalendarView_minDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800724 */
725 public long getMinDate() {
726 return mMinDate.getTimeInMillis();
727 }
728
729 /**
730 * Sets the minimal date supported by this {@link CalendarView} in milliseconds
731 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
732 * zone.
733 *
734 * @param minDate The minimal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700735 *
736 * @attr ref android.R.styleable#CalendarView_minDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800737 */
738 public void setMinDate(long minDate) {
739 mTempDate.setTimeInMillis(minDate);
740 if (isSameDate(mTempDate, mMinDate)) {
741 return;
742 }
743 mMinDate.setTimeInMillis(minDate);
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800744 // make sure the current date is not earlier than
745 // the new min date since the latter is used for
746 // calculating the indices in the adapter thus
747 // avoiding out of bounds error
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800748 Calendar date = mAdapter.mSelectedDate;
749 if (date.before(mMinDate)) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800750 mAdapter.setSelectedDay(mMinDate);
751 }
752 // reinitialize the adapter since its range depends on min date
753 mAdapter.init();
754 if (date.before(mMinDate)) {
755 setDate(mTempDate.getTimeInMillis());
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800756 } else {
757 // we go to the current date to force the ListView to query its
758 // adapter for the shown views since we have changed the adapter
759 // range and the base from which the later calculates item indices
760 // note that calling setDate will not work since the date is the same
761 goTo(date, false, true, false);
762 }
763 }
764
765 /**
766 * Gets the maximal date supported by this {@link CalendarView} in milliseconds
767 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
768 * zone.
769 * <p>
770 * Note: The default maximal date is 01/01/2100.
771 * <p>
772 *
773 * @return The maximal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700774 *
775 * @attr ref android.R.styleable#CalendarView_maxDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800776 */
777 public long getMaxDate() {
778 return mMaxDate.getTimeInMillis();
779 }
780
781 /**
782 * Sets the maximal date supported by this {@link CalendarView} in milliseconds
783 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
784 * zone.
785 *
786 * @param maxDate The maximal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700787 *
788 * @attr ref android.R.styleable#CalendarView_maxDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800789 */
790 public void setMaxDate(long maxDate) {
791 mTempDate.setTimeInMillis(maxDate);
792 if (isSameDate(mTempDate, mMaxDate)) {
793 return;
794 }
795 mMaxDate.setTimeInMillis(maxDate);
796 // reinitialize the adapter since its range depends on max date
797 mAdapter.init();
798 Calendar date = mAdapter.mSelectedDate;
799 if (date.after(mMaxDate)) {
800 setDate(mMaxDate.getTimeInMillis());
801 } else {
802 // we go to the current date to force the ListView to query its
803 // adapter for the shown views since we have changed the adapter
804 // range and the base from which the later calculates item indices
805 // note that calling setDate will not work since the date is the same
806 goTo(date, false, true, false);
807 }
808 }
809
810 /**
811 * Sets whether to show the week number.
812 *
813 * @param showWeekNumber True to show the week number.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700814 *
815 * @attr ref android.R.styleable#CalendarView_showWeekNumber
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800816 */
817 public void setShowWeekNumber(boolean showWeekNumber) {
818 if (mShowWeekNumber == showWeekNumber) {
819 return;
820 }
821 mShowWeekNumber = showWeekNumber;
822 mAdapter.notifyDataSetChanged();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800823 setUpHeader();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800824 }
825
826 /**
827 * Gets whether to show the week number.
828 *
829 * @return True if showing the week number.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700830 *
831 * @attr ref android.R.styleable#CalendarView_showWeekNumber
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800832 */
833 public boolean getShowWeekNumber() {
834 return mShowWeekNumber;
835 }
836
837 /**
838 * Gets the first day of week.
839 *
840 * @return The first day of the week conforming to the {@link CalendarView}
841 * APIs.
842 * @see Calendar#MONDAY
843 * @see Calendar#TUESDAY
844 * @see Calendar#WEDNESDAY
845 * @see Calendar#THURSDAY
846 * @see Calendar#FRIDAY
847 * @see Calendar#SATURDAY
848 * @see Calendar#SUNDAY
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700849 *
850 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800851 */
852 public int getFirstDayOfWeek() {
853 return mFirstDayOfWeek;
854 }
855
856 /**
857 * Sets the first day of week.
858 *
859 * @param firstDayOfWeek The first day of the week conforming to the
860 * {@link CalendarView} APIs.
861 * @see Calendar#MONDAY
862 * @see Calendar#TUESDAY
863 * @see Calendar#WEDNESDAY
864 * @see Calendar#THURSDAY
865 * @see Calendar#FRIDAY
866 * @see Calendar#SATURDAY
867 * @see Calendar#SUNDAY
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700868 *
869 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800870 */
871 public void setFirstDayOfWeek(int firstDayOfWeek) {
872 if (mFirstDayOfWeek == firstDayOfWeek) {
873 return;
874 }
875 mFirstDayOfWeek = firstDayOfWeek;
876 mAdapter.init();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800877 setUpHeader();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800878 }
879
880 /**
881 * Sets the listener to be notified upon selected date change.
882 *
883 * @param listener The listener to be notified.
884 */
885 public void setOnDateChangeListener(OnDateChangeListener listener) {
886 mOnDateChangeListener = listener;
887 }
888
889 /**
890 * Gets the selected date in milliseconds since January 1, 1970 00:00:00 in
891 * {@link TimeZone#getDefault()} time zone.
892 *
893 * @return The selected date.
894 */
895 public long getDate() {
896 return mAdapter.mSelectedDate.getTimeInMillis();
897 }
898
899 /**
900 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
901 * {@link TimeZone#getDefault()} time zone.
902 *
903 * @param date The selected date.
904 *
905 * @throws IllegalArgumentException of the provided date is before the
906 * minimal or after the maximal date.
907 *
908 * @see #setDate(long, boolean, boolean)
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800909 * @see #setMinDate(long)
910 * @see #setMaxDate(long)
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800911 */
912 public void setDate(long date) {
913 setDate(date, false, false);
914 }
915
916 /**
917 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
918 * {@link TimeZone#getDefault()} time zone.
919 *
920 * @param date The date.
921 * @param animate Whether to animate the scroll to the current date.
922 * @param center Whether to center the current date even if it is already visible.
923 *
924 * @throws IllegalArgumentException of the provided date is before the
925 * minimal or after the maximal date.
926 *
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800927 * @see #setMinDate(long)
928 * @see #setMaxDate(long)
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800929 */
930 public void setDate(long date, boolean animate, boolean center) {
931 mTempDate.setTimeInMillis(date);
932 if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
933 return;
934 }
935 goTo(mTempDate, animate, true, center);
936 }
937
Svetoslav Ganovff375052012-03-01 15:57:22 -0800938 private void updateDateTextSize() {
Alan Viverette22c1c2c2013-09-17 14:39:07 -0700939 TypedArray dateTextAppearance = mContext.obtainStyledAttributes(
Svetoslav Ganovff375052012-03-01 15:57:22 -0800940 mDateTextAppearanceResId, R.styleable.TextAppearance);
941 mDateTextSize = dateTextAppearance.getDimensionPixelSize(
942 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
943 dateTextAppearance.recycle();
944 }
945
946 /**
947 * Invalidates all week views.
948 */
949 private void invalidateAllWeekViews() {
950 final int childCount = mListView.getChildCount();
951 for (int i = 0; i < childCount; i++) {
952 View view = mListView.getChildAt(i);
953 view.invalidate();
954 }
955 }
956
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800957 /**
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700958 * Sets the current locale.
959 *
960 * @param locale The current locale.
961 */
962 private void setCurrentLocale(Locale locale) {
963 if (locale.equals(mCurrentLocale)) {
964 return;
965 }
966
967 mCurrentLocale = locale;
968
969 mTempDate = getCalendarForLocale(mTempDate, locale);
970 mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
971 mMinDate = getCalendarForLocale(mMinDate, locale);
972 mMaxDate = getCalendarForLocale(mMaxDate, locale);
973 }
974
975 /**
976 * Gets a calendar for locale bootstrapped with the value of a given calendar.
977 *
978 * @param oldCalendar The old calendar.
979 * @param locale The locale.
980 */
981 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
982 if (oldCalendar == null) {
983 return Calendar.getInstance(locale);
984 } else {
985 final long currentTimeMillis = oldCalendar.getTimeInMillis();
986 Calendar newCalendar = Calendar.getInstance(locale);
987 newCalendar.setTimeInMillis(currentTimeMillis);
988 return newCalendar;
989 }
990 }
991
992 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800993 * @return True if the <code>firstDate</code> is the same as the <code>
994 * secondDate</code>.
995 */
996 private boolean isSameDate(Calendar firstDate, Calendar secondDate) {
997 return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
998 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
999 }
1000
1001 /**
1002 * Creates a new adapter if necessary and sets up its parameters.
1003 */
1004 private void setUpAdapter() {
1005 if (mAdapter == null) {
Alan Viverette22c1c2c2013-09-17 14:39:07 -07001006 mAdapter = new WeeksAdapter();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001007 mAdapter.registerDataSetObserver(new DataSetObserver() {
1008 @Override
1009 public void onChanged() {
1010 if (mOnDateChangeListener != null) {
1011 Calendar selectedDay = mAdapter.getSelectedDay();
1012 mOnDateChangeListener.onSelectedDayChange(CalendarView.this,
1013 selectedDay.get(Calendar.YEAR),
1014 selectedDay.get(Calendar.MONTH),
1015 selectedDay.get(Calendar.DAY_OF_MONTH));
1016 }
1017 }
1018 });
1019 mListView.setAdapter(mAdapter);
1020 }
1021
1022 // refresh the view with the new parameters
1023 mAdapter.notifyDataSetChanged();
1024 }
1025
1026 /**
1027 * Sets up the strings to be used by the header.
1028 */
Svetoslav Ganovff375052012-03-01 15:57:22 -08001029 private void setUpHeader() {
Fabrice Di Megliob08e5782013-08-27 17:43:53 -07001030 final String[] tinyWeekdayNames = LocaleData.get(Locale.getDefault()).tinyWeekdayNames;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001031 mDayLabels = new String[mDaysPerWeek];
Fabrice Di Megliob08e5782013-08-27 17:43:53 -07001032 for (int i = 0; i < mDaysPerWeek; i++) {
1033 final int j = i + mFirstDayOfWeek;
1034 final int calendarDay = (j > Calendar.SATURDAY) ? j - Calendar.SATURDAY : j;
1035 mDayLabels[i] = tinyWeekdayNames[calendarDay];
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001036 }
Fabrice Di Megliob08e5782013-08-27 17:43:53 -07001037 // Deal with week number
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001038 TextView label = (TextView) mDayNamesHeader.getChildAt(0);
1039 if (mShowWeekNumber) {
1040 label.setVisibility(View.VISIBLE);
1041 } else {
1042 label.setVisibility(View.GONE);
1043 }
Fabrice Di Megliob08e5782013-08-27 17:43:53 -07001044 // Deal with day labels
1045 final int count = mDayNamesHeader.getChildCount();
1046 for (int i = 0; i < count - 1; i++) {
1047 label = (TextView) mDayNamesHeader.getChildAt(i + 1);
Svetoslav Ganovff375052012-03-01 15:57:22 -08001048 if (mWeekDayTextAppearanceResId > -1) {
1049 label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001050 }
Fabrice Di Megliob08e5782013-08-27 17:43:53 -07001051 if (i < mDaysPerWeek) {
1052 label.setText(mDayLabels[i]);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001053 label.setVisibility(View.VISIBLE);
1054 } else {
1055 label.setVisibility(View.GONE);
1056 }
1057 }
1058 mDayNamesHeader.invalidate();
1059 }
1060
1061 /**
1062 * Sets all the required fields for the list view.
1063 */
1064 private void setUpListView() {
1065 // Configure the listview
1066 mListView.setDivider(null);
1067 mListView.setItemsCanFocus(true);
1068 mListView.setVerticalScrollBarEnabled(false);
1069 mListView.setOnScrollListener(new OnScrollListener() {
1070 public void onScrollStateChanged(AbsListView view, int scrollState) {
1071 CalendarView.this.onScrollStateChanged(view, scrollState);
1072 }
1073
1074 public void onScroll(
1075 AbsListView view, int firstVisibleItem, int visibleItemCount,
1076 int totalItemCount) {
1077 CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount,
1078 totalItemCount);
1079 }
1080 });
1081 // Make the scrolling behavior nicer
1082 mListView.setFriction(mFriction);
1083 mListView.setVelocityScale(mVelocityScale);
1084 }
1085
1086 /**
1087 * This moves to the specified time in the view. If the time is not already
1088 * in range it will move the list so that the first of the month containing
1089 * the time is at the top of the view. If the new time is already in view
1090 * the list will not be scrolled unless forceScroll is true. This time may
1091 * optionally be highlighted as selected as well.
1092 *
1093 * @param date The time to move to.
1094 * @param animate Whether to scroll to the given time or just redraw at the
1095 * new location.
1096 * @param setSelected Whether to set the given time as selected.
1097 * @param forceScroll Whether to recenter even if the time is already
1098 * visible.
1099 *
1100 * @throws IllegalArgumentException of the provided date is before the
1101 * range start of after the range end.
1102 */
1103 private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) {
1104 if (date.before(mMinDate) || date.after(mMaxDate)) {
1105 throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
1106 + " and " + mMaxDate.getTime());
1107 }
1108 // Find the first and last entirely visible weeks
1109 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
1110 View firstChild = mListView.getChildAt(0);
1111 if (firstChild != null && firstChild.getTop() < 0) {
1112 firstFullyVisiblePosition++;
1113 }
1114 int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
1115 if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
1116 lastFullyVisiblePosition--;
1117 }
1118 if (setSelected) {
1119 mAdapter.setSelectedDay(date);
1120 }
1121 // Get the week we're going to
1122 int position = getWeeksSinceMinDate(date);
1123
1124 // Check if the selected day is now outside of our visible range
1125 // and if so scroll to the month that contains it
1126 if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
1127 || forceScroll) {
1128 mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
1129 mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
1130
1131 setMonthDisplayed(mFirstDayOfMonth);
Svetoslav Ganov156f2092011-01-24 02:13:36 -08001132
1133 // the earliest time we can scroll to is the min date
1134 if (mFirstDayOfMonth.before(mMinDate)) {
1135 position = 0;
1136 } else {
1137 position = getWeeksSinceMinDate(mFirstDayOfMonth);
1138 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001139
1140 mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
1141 if (animate) {
1142 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
1143 GOTO_SCROLL_DURATION);
1144 } else {
1145 mListView.setSelectionFromTop(position, mListScrollTopOffset);
1146 // Perform any after scroll operations that are needed
1147 onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
1148 }
1149 } else if (setSelected) {
1150 // Otherwise just set the selection
1151 setMonthDisplayed(date);
1152 }
1153 }
1154
1155 /**
1156 * Parses the given <code>date</code> and in case of success sets
1157 * the result to the <code>outDate</code>.
1158 *
1159 * @return True if the date was parsed.
1160 */
1161 private boolean parseDate(String date, Calendar outDate) {
1162 try {
1163 outDate.setTime(mDateFormat.parse(date));
1164 return true;
1165 } catch (ParseException e) {
1166 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
1167 return false;
1168 }
1169 }
1170
1171 /**
1172 * Called when a <code>view</code> transitions to a new <code>scrollState
1173 * </code>.
1174 */
1175 private void onScrollStateChanged(AbsListView view, int scrollState) {
1176 mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
1177 }
1178
1179 /**
1180 * Updates the title and selected month if the <code>view</code> has moved to a new
1181 * month.
1182 */
1183 private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1184 int totalItemCount) {
1185 WeekView child = (WeekView) view.getChildAt(0);
1186 if (child == null) {
1187 return;
1188 }
1189
1190 // Figure out where we are
1191 long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
1192
1193 // If we have moved since our last call update the direction
1194 if (currScroll < mPreviousScrollPosition) {
1195 mIsScrollingUp = true;
1196 } else if (currScroll > mPreviousScrollPosition) {
1197 mIsScrollingUp = false;
1198 } else {
1199 return;
1200 }
1201
1202 // Use some hysteresis for checking which month to highlight. This
1203 // causes the month to transition when two full weeks of a month are
1204 // visible when scrolling up, and when the first day in a month reaches
1205 // the top of the screen when scrolling down.
1206 int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
1207 if (mIsScrollingUp) {
1208 child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
1209 } else if (offset != 0) {
1210 child = (WeekView) view.getChildAt(offset);
1211 }
1212
1213 // Find out which month we're moving into
1214 int month;
1215 if (mIsScrollingUp) {
1216 month = child.getMonthOfFirstWeekDay();
1217 } else {
1218 month = child.getMonthOfLastWeekDay();
1219 }
1220
1221 // And how it relates to our current highlighted month
1222 int monthDiff;
1223 if (mCurrentMonthDisplayed == 11 && month == 0) {
1224 monthDiff = 1;
1225 } else if (mCurrentMonthDisplayed == 0 && month == 11) {
1226 monthDiff = -1;
1227 } else {
1228 monthDiff = month - mCurrentMonthDisplayed;
1229 }
1230
1231 // Only switch months if we're scrolling away from the currently
1232 // selected month
1233 if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
1234 Calendar firstDay = child.getFirstDay();
1235 if (mIsScrollingUp) {
1236 firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
1237 } else {
1238 firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
1239 }
1240 setMonthDisplayed(firstDay);
1241 }
1242 mPreviousScrollPosition = currScroll;
1243 mPreviousScrollState = mCurrentScrollState;
1244 }
1245
1246 /**
1247 * Sets the month displayed at the top of this view based on time. Override
1248 * to add custom events when the title is changed.
1249 *
1250 * @param calendar A day in the new focus month.
1251 */
1252 private void setMonthDisplayed(Calendar calendar) {
Svetoslava0851792013-05-29 14:37:30 -07001253 mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
1254 mAdapter.setFocusMonth(mCurrentMonthDisplayed);
1255 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
1256 | DateUtils.FORMAT_SHOW_YEAR;
1257 final long millis = calendar.getTimeInMillis();
1258 String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
1259 mMonthName.setText(newMonthName);
1260 mMonthName.invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001261 }
1262
1263 /**
1264 * @return Returns the number of weeks between the current <code>date</code>
1265 * and the <code>mMinDate</code>.
1266 */
1267 private int getWeeksSinceMinDate(Calendar date) {
1268 if (date.before(mMinDate)) {
1269 throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
1270 + " does not precede toDate: " + date.getTime());
1271 }
Svetoslav Ganov58f51252011-01-26 22:50:51 -08001272 long endTimeMillis = date.getTimeInMillis()
1273 + date.getTimeZone().getOffset(date.getTimeInMillis());
1274 long startTimeMillis = mMinDate.getTimeInMillis()
1275 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
1276 long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
1277 * MILLIS_IN_DAY;
1278 return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001279 }
1280
1281 /**
1282 * Command responsible for acting upon scroll state changes.
1283 */
1284 private class ScrollStateRunnable implements Runnable {
1285 private AbsListView mView;
1286
1287 private int mNewState;
1288
1289 /**
1290 * Sets up the runnable with a short delay in case the scroll state
1291 * immediately changes again.
1292 *
1293 * @param view The list view that changed state
1294 * @param scrollState The new state it changed to
1295 */
1296 public void doScrollStateChange(AbsListView view, int scrollState) {
1297 mView = view;
1298 mNewState = scrollState;
1299 removeCallbacks(this);
1300 postDelayed(this, SCROLL_CHANGE_DELAY);
1301 }
1302
1303 public void run() {
1304 mCurrentScrollState = mNewState;
1305 // Fix the position after a scroll or a fling ends
1306 if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
1307 && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
1308 View child = mView.getChildAt(0);
1309 if (child == null) {
1310 // The view is no longer visible, just return
1311 return;
1312 }
1313 int dist = child.getBottom() - mListScrollTopOffset;
1314 if (dist > mListScrollTopOffset) {
1315 if (mIsScrollingUp) {
1316 mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION);
1317 } else {
1318 mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
1319 }
1320 }
1321 }
1322 mPreviousScrollState = mNewState;
1323 }
1324 }
1325
1326 /**
1327 * <p>
1328 * This is a specialized adapter for creating a list of weeks with
1329 * selectable days. It can be configured to display the week number, start
1330 * the week on a given day, show a reduced number of days, or display an
1331 * arbitrary number of weeks at a time.
1332 * </p>
1333 */
1334 private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
Alan Viverette22c1c2c2013-09-17 14:39:07 -07001335 private final Calendar mSelectedDate = Calendar.getInstance();
1336 private final GestureDetector mGestureDetector;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001337
1338 private int mSelectedWeek;
1339
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001340 private int mFocusedMonth;
1341
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001342 private int mTotalWeekCount;
1343
Alan Viverette22c1c2c2013-09-17 14:39:07 -07001344 public WeeksAdapter() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001345 mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
1346 init();
1347 }
1348
1349 /**
1350 * Set up the gesture detector and selected time
1351 */
1352 private void init() {
1353 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1354 mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
1355 if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
1356 || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
1357 mTotalWeekCount++;
1358 }
Alan Viverette22c1c2c2013-09-17 14:39:07 -07001359 notifyDataSetChanged();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001360 }
1361
1362 /**
1363 * Updates the selected day and related parameters.
1364 *
1365 * @param selectedDay The time to highlight
1366 */
1367 public void setSelectedDay(Calendar selectedDay) {
1368 if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
1369 && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
1370 return;
1371 }
1372 mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
1373 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1374 mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
1375 notifyDataSetChanged();
1376 }
1377
1378 /**
1379 * @return The selected day of month.
1380 */
1381 public Calendar getSelectedDay() {
1382 return mSelectedDate;
1383 }
1384
1385 @Override
1386 public int getCount() {
1387 return mTotalWeekCount;
1388 }
1389
1390 @Override
1391 public Object getItem(int position) {
1392 return null;
1393 }
1394
1395 @Override
1396 public long getItemId(int position) {
1397 return position;
1398 }
1399
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001400 @Override
1401 public View getView(int position, View convertView, ViewGroup parent) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001402 WeekView weekView = null;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001403 if (convertView != null) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001404 weekView = (WeekView) convertView;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001405 } else {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001406 weekView = new WeekView(mContext);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001407 android.widget.AbsListView.LayoutParams params =
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001408 new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
1409 LayoutParams.WRAP_CONTENT);
1410 weekView.setLayoutParams(params);
1411 weekView.setClickable(true);
1412 weekView.setOnTouchListener(this);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001413 }
1414
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001415 int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1416 Calendar.DAY_OF_WEEK) : -1;
1417 weekView.init(position, selectedWeekDay, mFocusedMonth);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001418
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001419 return weekView;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001420 }
1421
1422 /**
1423 * Changes which month is in focus and updates the view.
1424 *
1425 * @param month The month to show as in focus [0-11]
1426 */
1427 public void setFocusMonth(int month) {
1428 if (mFocusedMonth == month) {
1429 return;
1430 }
1431 mFocusedMonth = month;
1432 notifyDataSetChanged();
1433 }
1434
1435 @Override
1436 public boolean onTouch(View v, MotionEvent event) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001437 if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001438 WeekView weekView = (WeekView) v;
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001439 // if we cannot find a day for the given location we are done
1440 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
1441 return true;
1442 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001443 // it is possible that the touched day is outside the valid range
1444 // we draw whole weeks but range end can fall not on the week end
1445 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1446 return true;
1447 }
1448 onDateTapped(mTempDate);
1449 return true;
1450 }
1451 return false;
1452 }
1453
1454 /**
1455 * Maintains the same hour/min/sec but moves the day to the tapped day.
1456 *
1457 * @param day The day that was tapped
1458 */
1459 private void onDateTapped(Calendar day) {
1460 setSelectedDay(day);
1461 setMonthDisplayed(day);
1462 }
1463
1464 /**
1465 * This is here so we can identify single tap events and set the
1466 * selected day correctly
1467 */
1468 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
1469 @Override
1470 public boolean onSingleTapUp(MotionEvent e) {
1471 return true;
1472 }
1473 }
1474 }
1475
1476 /**
1477 * <p>
1478 * This is a dynamic view for drawing a single week. It can be configured to
1479 * display the week number, start the week on a given day, or show a reduced
1480 * number of days. It is intended for use as a single view within a
1481 * ListView. See {@link WeeksAdapter} for usage.
1482 * </p>
1483 */
1484 private class WeekView extends View {
1485
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001486 private final Rect mTempRect = new Rect();
1487
1488 private final Paint mDrawPaint = new Paint();
1489
1490 private final Paint mMonthNumDrawPaint = new Paint();
1491
1492 // Cache the number strings so we don't have to recompute them each time
1493 private String[] mDayNumbers;
1494
1495 // Quick lookup for checking which days are in the focus month
1496 private boolean[] mFocusDay;
1497
Svetoslav Ganovff375052012-03-01 15:57:22 -08001498 // Whether this view has a focused day.
1499 private boolean mHasFocusedDay;
1500
1501 // Whether this view has only focused days.
1502 private boolean mHasUnfocusedDay;
1503
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001504 // The first day displayed by this item
1505 private Calendar mFirstDay;
1506
1507 // The month of the first day in this week
1508 private int mMonthOfFirstWeekDay = -1;
1509
1510 // The month of the last day in this week
1511 private int mLastWeekDayMonth = -1;
1512
1513 // The position of this week, equivalent to weeks since the week of Jan
1514 // 1st, 1900
1515 private int mWeek = -1;
1516
1517 // Quick reference to the width of this view, matches parent
1518 private int mWidth;
1519
1520 // The height this view should draw at in pixels, set by height param
1521 private int mHeight;
1522
1523 // If this view contains the selected day
1524 private boolean mHasSelectedDay = false;
1525
1526 // Which day is selected [0-6] or -1 if no day is selected
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001527 private int mSelectedDay = -1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001528
1529 // The number of days + a spot for week number if it is displayed
1530 private int mNumCells;
1531
1532 // The left edge of the selected day
1533 private int mSelectedLeft = -1;
1534
1535 // The right edge of the selected day
1536 private int mSelectedRight = -1;
1537
1538 public WeekView(Context context) {
1539 super(context);
1540
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001541 // Sets up any standard paints that will be used
Svetoslav Ganovff375052012-03-01 15:57:22 -08001542 initilaizePaints();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001543 }
1544
1545 /**
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001546 * Initializes this week view.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001547 *
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001548 * @param weekNumber The number of the week this view represents. The
1549 * week number is a zero based index of the weeks since
1550 * {@link CalendarView#getMinDate()}.
1551 * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
1552 * selected day.
1553 * @param focusedMonth The month that is currently in focus i.e.
1554 * highlighted.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001555 */
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001556 public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
1557 mSelectedDay = selectedWeekDay;
1558 mHasSelectedDay = mSelectedDay != -1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001559 mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001560 mWeek = weekNumber;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001561 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
Svetoslav Ganov58f51252011-01-26 22:50:51 -08001562
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001563 mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1564 mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
1565
1566 // Allocate space for caching the day numbers and focus values
1567 mDayNumbers = new String[mNumCells];
1568 mFocusDay = new boolean[mNumCells];
1569
1570 // If we're showing the week number calculate it based on Monday
1571 int i = 0;
1572 if (mShowWeekNumber) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001573 mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1574 mTempDate.get(Calendar.WEEK_OF_YEAR));
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001575 i++;
1576 }
1577
1578 // Now adjust our starting day based on the start day of the week
1579 int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
1580 mTempDate.add(Calendar.DAY_OF_MONTH, diff);
1581
1582 mFirstDay = (Calendar) mTempDate.clone();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001583 mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
1584
Svetoslav Ganovff375052012-03-01 15:57:22 -08001585 mHasUnfocusedDay = true;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001586 for (; i < mNumCells; i++) {
Svetoslav Ganovff375052012-03-01 15:57:22 -08001587 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
1588 mFocusDay[i] = isFocusedDay;
1589 mHasFocusedDay |= isFocusedDay;
1590 mHasUnfocusedDay &= !isFocusedDay;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001591 // do not draw dates outside the valid range to avoid user confusion
1592 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1593 mDayNumbers[i] = "";
1594 } else {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001595 mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
1596 mTempDate.get(Calendar.DAY_OF_MONTH));
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001597 }
1598 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
1599 }
1600 // We do one extra add at the end of the loop, if that pushed us to
1601 // new month undo it
1602 if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
1603 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
1604 }
1605 mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
1606
1607 updateSelectionPositions();
1608 }
1609
1610 /**
Svetoslav Ganov0c6f3792012-09-21 11:49:01 -07001611 * Initialize the paint instances.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001612 */
Svetoslav Ganovff375052012-03-01 15:57:22 -08001613 private void initilaizePaints() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001614 mDrawPaint.setFakeBoldText(false);
1615 mDrawPaint.setAntiAlias(true);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001616 mDrawPaint.setStyle(Style.FILL);
1617
1618 mMonthNumDrawPaint.setFakeBoldText(true);
1619 mMonthNumDrawPaint.setAntiAlias(true);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001620 mMonthNumDrawPaint.setStyle(Style.FILL);
1621 mMonthNumDrawPaint.setTextAlign(Align.CENTER);
Svetoslav Ganov0c6f3792012-09-21 11:49:01 -07001622 mMonthNumDrawPaint.setTextSize(mDateTextSize);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001623 }
1624
1625 /**
1626 * Returns the month of the first day in this week.
1627 *
1628 * @return The month the first day of this view is in.
1629 */
1630 public int getMonthOfFirstWeekDay() {
1631 return mMonthOfFirstWeekDay;
1632 }
1633
1634 /**
1635 * Returns the month of the last day in this week
1636 *
1637 * @return The month the last day of this view is in
1638 */
1639 public int getMonthOfLastWeekDay() {
1640 return mLastWeekDayMonth;
1641 }
1642
1643 /**
1644 * Returns the first day in this view.
1645 *
1646 * @return The first day in the view.
1647 */
1648 public Calendar getFirstDay() {
1649 return mFirstDay;
1650 }
1651
1652 /**
1653 * Calculates the day that the given x position is in, accounting for
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001654 * week number.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001655 *
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001656 * @param x The x position of the touch event.
1657 * @return True if a day was found for the given location.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001658 */
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001659 public boolean getDayFromLocation(float x, Calendar outCalendar) {
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001660 final boolean isLayoutRtl = isLayoutRtl();
1661
1662 int start;
1663 int end;
1664
1665 if (isLayoutRtl) {
1666 start = 0;
1667 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1668 } else {
1669 start = mShowWeekNumber ? mWidth / mNumCells : 0;
1670 end = mWidth;
1671 }
1672
1673 if (x < start || x > end) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001674 outCalendar.clear();
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001675 return false;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001676 }
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001677
1678 // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
1679 int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
1680
1681 if (isLayoutRtl) {
1682 dayPosition = mDaysPerWeek - 1 - dayPosition;
1683 }
1684
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001685 outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
1686 outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001687
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001688 return true;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001689 }
1690
1691 @Override
1692 protected void onDraw(Canvas canvas) {
1693 drawBackground(canvas);
Svetoslav Ganovff375052012-03-01 15:57:22 -08001694 drawWeekNumbersAndDates(canvas);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001695 drawWeekSeparators(canvas);
1696 drawSelectedDateVerticalBars(canvas);
1697 }
1698
1699 /**
1700 * This draws the selection highlight if a day is selected in this week.
1701 *
1702 * @param canvas The canvas to draw on
1703 */
1704 private void drawBackground(Canvas canvas) {
1705 if (!mHasSelectedDay) {
1706 return;
1707 }
1708 mDrawPaint.setColor(mSelectedWeekBackgroundColor);
1709
1710 mTempRect.top = mWeekSeperatorLineWidth;
1711 mTempRect.bottom = mHeight;
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001712
1713 final boolean isLayoutRtl = isLayoutRtl();
1714
1715 if (isLayoutRtl) {
1716 mTempRect.left = 0;
1717 mTempRect.right = mSelectedLeft - 2;
1718 } else {
1719 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
1720 mTempRect.right = mSelectedLeft - 2;
1721 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001722 canvas.drawRect(mTempRect, mDrawPaint);
1723
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001724 if (isLayoutRtl) {
1725 mTempRect.left = mSelectedRight + 3;
1726 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1727 } else {
1728 mTempRect.left = mSelectedRight + 3;
1729 mTempRect.right = mWidth;
1730 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001731 canvas.drawRect(mTempRect, mDrawPaint);
1732 }
1733
1734 /**
1735 * Draws the week and month day numbers for this week.
1736 *
1737 * @param canvas The canvas to draw on
1738 */
Svetoslav Ganovff375052012-03-01 15:57:22 -08001739 private void drawWeekNumbersAndDates(Canvas canvas) {
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001740 final float textHeight = mDrawPaint.getTextSize();
1741 final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
1742 final int nDays = mNumCells;
1743 final int divisor = 2 * nDays;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001744
1745 mDrawPaint.setTextAlign(Align.CENTER);
Svetoslav Ganovff375052012-03-01 15:57:22 -08001746 mDrawPaint.setTextSize(mDateTextSize);
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001747
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001748 int i = 0;
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001749
1750 if (isLayoutRtl()) {
1751 for (; i < nDays - 1; i++) {
1752 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
1753 : mUnfocusedMonthDateColor);
1754 int x = (2 * i + 1) * mWidth / divisor;
1755 canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
1756 }
1757 if (mShowWeekNumber) {
1758 mDrawPaint.setColor(mWeekNumberColor);
1759 int x = mWidth - mWidth / divisor;
1760 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
1761 }
1762 } else {
1763 if (mShowWeekNumber) {
1764 mDrawPaint.setColor(mWeekNumberColor);
1765 int x = mWidth / divisor;
1766 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
1767 i++;
1768 }
1769 for (; i < nDays; i++) {
1770 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
1771 : mUnfocusedMonthDateColor);
1772 int x = (2 * i + 1) * mWidth / divisor;
1773 canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
1774 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001775 }
1776 }
1777
1778 /**
1779 * Draws a horizontal line for separating the weeks.
1780 *
1781 * @param canvas The canvas to draw on.
1782 */
1783 private void drawWeekSeparators(Canvas canvas) {
1784 // If it is the topmost fully visible child do not draw separator line
1785 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
1786 if (mListView.getChildAt(0).getTop() < 0) {
1787 firstFullyVisiblePosition++;
1788 }
1789 if (firstFullyVisiblePosition == mWeek) {
1790 return;
1791 }
1792 mDrawPaint.setColor(mWeekSeparatorLineColor);
1793 mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001794 float startX;
1795 float stopX;
1796 if (isLayoutRtl()) {
1797 startX = 0;
1798 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1799 } else {
1800 startX = mShowWeekNumber ? mWidth / mNumCells : 0;
1801 stopX = mWidth;
1802 }
1803 canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001804 }
1805
1806 /**
1807 * Draws the selected date bars if this week has a selected day.
1808 *
1809 * @param canvas The canvas to draw on
1810 */
1811 private void drawSelectedDateVerticalBars(Canvas canvas) {
1812 if (!mHasSelectedDay) {
1813 return;
1814 }
1815 mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
1816 mWeekSeperatorLineWidth,
1817 mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight);
1818 mSelectedDateVerticalBar.draw(canvas);
1819 mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2,
1820 mWeekSeperatorLineWidth,
1821 mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight);
1822 mSelectedDateVerticalBar.draw(canvas);
1823 }
1824
1825 @Override
1826 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1827 mWidth = w;
1828 updateSelectionPositions();
1829 }
1830
1831 /**
1832 * This calculates the positions for the selected day lines.
1833 */
1834 private void updateSelectionPositions() {
1835 if (mHasSelectedDay) {
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001836 final boolean isLayoutRtl = isLayoutRtl();
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -08001837 int selectedPosition = mSelectedDay - mFirstDayOfWeek;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001838 if (selectedPosition < 0) {
1839 selectedPosition += 7;
1840 }
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001841 if (mShowWeekNumber && !isLayoutRtl) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001842 selectedPosition++;
1843 }
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001844 if (isLayoutRtl) {
1845 mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
1846
1847 } else {
1848 mSelectedLeft = selectedPosition * mWidth / mNumCells;
1849 }
1850 mSelectedRight = mSelectedLeft + mWidth / mNumCells;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001851 }
1852 }
1853
1854 @Override
1855 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Svetoslav Ganovff375052012-03-01 15:57:22 -08001856 mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
1857 .getPaddingBottom()) / mShownWeekCount;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001858 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
1859 }
1860 }
1861}