blob: ea60abbfb14405331dce5dc5fd82f8d0c103cf32 [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
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -070083 private CalendarViewDelegate mDelegate;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070084
85 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080086 * The callback used to indicate the user changes the date.
87 */
88 public interface OnDateChangeListener {
89
90 /**
91 * Called upon change of the selected day.
92 *
93 * @param view The view associated with this listener.
94 * @param year The year that was set.
95 * @param month The month that was set [0-11].
96 * @param dayOfMonth The day of the month that was set.
97 */
98 public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth);
99 }
100
101 public CalendarView(Context context) {
102 this(context, null);
103 }
104
105 public CalendarView(Context context, AttributeSet attrs) {
Alan Viverette617feb92013-09-09 18:09:13 -0700106 this(context, attrs, R.attr.calendarViewStyle);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800107 }
108
Alan Viverette617feb92013-09-09 18:09:13 -0700109 public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
110 this(context, attrs, defStyleAttr, 0);
111 }
112
113 public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
114 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800115
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700116 mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800117 }
118
Svetoslav Ganovff375052012-03-01 15:57:22 -0800119 /**
120 * Sets the number of weeks to be shown.
121 *
122 * @param count The shown week count.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700123 *
124 * @attr ref android.R.styleable#CalendarView_shownWeekCount
Svetoslav Ganovff375052012-03-01 15:57:22 -0800125 */
126 public void setShownWeekCount(int count) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700127 mDelegate.setShownWeekCount(count);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800128 }
129
130 /**
131 * Gets the number of weeks to be shown.
132 *
133 * @return The shown week count.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700134 *
135 * @attr ref android.R.styleable#CalendarView_shownWeekCount
Svetoslav Ganovff375052012-03-01 15:57:22 -0800136 */
137 public int getShownWeekCount() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700138 return mDelegate.getShownWeekCount();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800139 }
140
141 /**
142 * Sets the background color for the selected week.
143 *
144 * @param color The week background color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700145 *
146 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800147 */
148 public void setSelectedWeekBackgroundColor(int color) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700149 mDelegate.setSelectedWeekBackgroundColor(color);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800150 }
151
152 /**
153 * Gets the background color for the selected week.
154 *
155 * @return The week background color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700156 *
157 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800158 */
159 public int getSelectedWeekBackgroundColor() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700160 return mDelegate.getSelectedWeekBackgroundColor();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800161 }
162
163 /**
164 * Sets the color for the dates of the focused month.
165 *
166 * @param color The focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700167 *
168 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800169 */
170 public void setFocusedMonthDateColor(int color) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700171 mDelegate.setFocusedMonthDateColor(color);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800172 }
173
174 /**
175 * Gets the color for the dates in the focused month.
176 *
177 * @return The focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700178 *
179 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800180 */
181 public int getFocusedMonthDateColor() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700182 return mDelegate.getFocusedMonthDateColor();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800183 }
184
185 /**
186 * Sets the color for the dates of a not focused month.
187 *
188 * @param color A not focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700189 *
190 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800191 */
192 public void setUnfocusedMonthDateColor(int color) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700193 mDelegate.setUnfocusedMonthDateColor(color);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800194 }
195
196 /**
197 * Gets the color for the dates in a not focused month.
198 *
199 * @return A not focused month date color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700200 *
201 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800202 */
203 public int getUnfocusedMonthDateColor() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700204 return mDelegate.getUnfocusedMonthDateColor();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800205 }
206
207 /**
208 * Sets the color for the week numbers.
209 *
210 * @param color The week number color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700211 *
212 * @attr ref android.R.styleable#CalendarView_weekNumberColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800213 */
214 public void setWeekNumberColor(int color) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700215 mDelegate.setWeekNumberColor(color);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800216 }
217
218 /**
219 * Gets the color for the week numbers.
220 *
221 * @return The week number color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700222 *
223 * @attr ref android.R.styleable#CalendarView_weekNumberColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800224 */
225 public int getWeekNumberColor() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700226 return mDelegate.getWeekNumberColor();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800227 }
228
229 /**
230 * Sets the color for the separator line between weeks.
231 *
232 * @param color The week separator color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700233 *
234 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800235 */
236 public void setWeekSeparatorLineColor(int color) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700237 mDelegate.setWeekSeparatorLineColor(color);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800238 }
239
240 /**
241 * Gets the color for the separator line between weeks.
242 *
243 * @return The week separator color.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700244 *
245 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
Svetoslav Ganovff375052012-03-01 15:57:22 -0800246 */
247 public int getWeekSeparatorLineColor() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700248 return mDelegate.getWeekSeparatorLineColor();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800249 }
250
251 /**
252 * Sets the drawable for the vertical bar shown at the beginning and at
253 * the end of the selected date.
254 *
255 * @param resourceId The vertical bar drawable resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700256 *
257 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
Svetoslav Ganovff375052012-03-01 15:57:22 -0800258 */
259 public void setSelectedDateVerticalBar(int resourceId) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700260 mDelegate.setSelectedDateVerticalBar(resourceId);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800261 }
262
263 /**
264 * Sets the drawable for the vertical bar shown at the beginning and at
265 * the end of the selected date.
266 *
267 * @param drawable The vertical bar drawable.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700268 *
269 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
Svetoslav Ganovff375052012-03-01 15:57:22 -0800270 */
271 public void setSelectedDateVerticalBar(Drawable drawable) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700272 mDelegate.setSelectedDateVerticalBar(drawable);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800273 }
274
275 /**
276 * Gets the drawable for the vertical bar shown at the beginning and at
277 * the end of the selected date.
278 *
279 * @return The vertical bar drawable.
280 */
281 public Drawable getSelectedDateVerticalBar() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700282 return mDelegate.getSelectedDateVerticalBar();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800283 }
284
285 /**
286 * Sets the text appearance for the week day abbreviation of the calendar header.
287 *
288 * @param resourceId The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700289 *
290 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800291 */
292 public void setWeekDayTextAppearance(int resourceId) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700293 mDelegate.setWeekDayTextAppearance(resourceId);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800294 }
295
296 /**
297 * Gets the text appearance for the week day abbreviation of the calendar header.
298 *
299 * @return The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700300 *
301 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800302 */
303 public int getWeekDayTextAppearance() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700304 return mDelegate.getWeekDayTextAppearance();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800305 }
306
307 /**
308 * Sets the text appearance for the calendar dates.
309 *
310 * @param resourceId The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700311 *
312 * @attr ref android.R.styleable#CalendarView_dateTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800313 */
314 public void setDateTextAppearance(int resourceId) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700315 mDelegate.setDateTextAppearance(resourceId);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800316 }
317
318 /**
319 * Gets the text appearance for the calendar dates.
320 *
321 * @return The text appearance resource id.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700322 *
323 * @attr ref android.R.styleable#CalendarView_dateTextAppearance
Svetoslav Ganovff375052012-03-01 15:57:22 -0800324 */
325 public int getDateTextAppearance() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700326 return mDelegate.getDateTextAppearance();
Svetoslav Ganovff375052012-03-01 15:57:22 -0800327 }
328
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800329 @Override
330 public void setEnabled(boolean enabled) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700331 mDelegate.setEnabled(enabled);
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800332 }
333
334 @Override
335 public boolean isEnabled() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700336 return mDelegate.isEnabled();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800337 }
338
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800339 /**
340 * Gets the minimal date supported by this {@link CalendarView} in milliseconds
341 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
342 * zone.
343 * <p>
344 * Note: The default minimal date is 01/01/1900.
345 * <p>
346 *
347 * @return The minimal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700348 *
349 * @attr ref android.R.styleable#CalendarView_minDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800350 */
351 public long getMinDate() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700352 return mDelegate.getMinDate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800353 }
354
355 /**
356 * Sets the minimal date supported by this {@link CalendarView} in milliseconds
357 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
358 * zone.
359 *
360 * @param minDate The minimal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700361 *
362 * @attr ref android.R.styleable#CalendarView_minDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800363 */
364 public void setMinDate(long minDate) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700365 mDelegate.setMinDate(minDate);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800366 }
367
368 /**
369 * Gets the maximal date supported by this {@link CalendarView} in milliseconds
370 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
371 * zone.
372 * <p>
373 * Note: The default maximal date is 01/01/2100.
374 * <p>
375 *
376 * @return The maximal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700377 *
378 * @attr ref android.R.styleable#CalendarView_maxDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800379 */
380 public long getMaxDate() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700381 return mDelegate.getMaxDate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800382 }
383
384 /**
385 * Sets the maximal date supported by this {@link CalendarView} in milliseconds
386 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
387 * zone.
388 *
389 * @param maxDate The maximal supported date.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700390 *
391 * @attr ref android.R.styleable#CalendarView_maxDate
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800392 */
393 public void setMaxDate(long maxDate) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700394 mDelegate.setMaxDate(maxDate);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800395 }
396
397 /**
398 * Sets whether to show the week number.
399 *
400 * @param showWeekNumber True to show the week number.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700401 *
402 * @attr ref android.R.styleable#CalendarView_showWeekNumber
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800403 */
404 public void setShowWeekNumber(boolean showWeekNumber) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700405 mDelegate.setShowWeekNumber(showWeekNumber);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800406 }
407
408 /**
409 * Gets whether to show the week number.
410 *
411 * @return True if showing the week number.
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700412 *
413 * @attr ref android.R.styleable#CalendarView_showWeekNumber
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800414 */
415 public boolean getShowWeekNumber() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700416 return mDelegate.getShowWeekNumber();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800417 }
418
419 /**
420 * Gets the first day of week.
421 *
422 * @return The first day of the week conforming to the {@link CalendarView}
423 * APIs.
424 * @see Calendar#MONDAY
425 * @see Calendar#TUESDAY
426 * @see Calendar#WEDNESDAY
427 * @see Calendar#THURSDAY
428 * @see Calendar#FRIDAY
429 * @see Calendar#SATURDAY
430 * @see Calendar#SUNDAY
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700431 *
432 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800433 */
434 public int getFirstDayOfWeek() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700435 return mDelegate.getFirstDayOfWeek();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800436 }
437
438 /**
439 * Sets the first day of week.
440 *
441 * @param firstDayOfWeek The first day of the week conforming to the
442 * {@link CalendarView} APIs.
443 * @see Calendar#MONDAY
444 * @see Calendar#TUESDAY
445 * @see Calendar#WEDNESDAY
446 * @see Calendar#THURSDAY
447 * @see Calendar#FRIDAY
448 * @see Calendar#SATURDAY
449 * @see Calendar#SUNDAY
Svetoslav Ganov414efc32012-05-04 16:49:48 -0700450 *
451 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800452 */
453 public void setFirstDayOfWeek(int firstDayOfWeek) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700454 mDelegate.setFirstDayOfWeek(firstDayOfWeek);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800455 }
456
457 /**
458 * Sets the listener to be notified upon selected date change.
459 *
460 * @param listener The listener to be notified.
461 */
462 public void setOnDateChangeListener(OnDateChangeListener listener) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700463 mDelegate.setOnDateChangeListener(listener);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800464 }
465
466 /**
467 * Gets the selected date in milliseconds since January 1, 1970 00:00:00 in
468 * {@link TimeZone#getDefault()} time zone.
469 *
470 * @return The selected date.
471 */
472 public long getDate() {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700473 return mDelegate.getDate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800474 }
475
476 /**
477 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
478 * {@link TimeZone#getDefault()} time zone.
479 *
480 * @param date The selected date.
481 *
482 * @throws IllegalArgumentException of the provided date is before the
483 * minimal or after the maximal date.
484 *
485 * @see #setDate(long, boolean, boolean)
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800486 * @see #setMinDate(long)
487 * @see #setMaxDate(long)
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800488 */
489 public void setDate(long date) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700490 mDelegate.setDate(date);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800491 }
492
493 /**
494 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
495 * {@link TimeZone#getDefault()} time zone.
496 *
497 * @param date The date.
498 * @param animate Whether to animate the scroll to the current date.
499 * @param center Whether to center the current date even if it is already visible.
500 *
501 * @throws IllegalArgumentException of the provided date is before the
502 * minimal or after the maximal date.
503 *
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800504 * @see #setMinDate(long)
505 * @see #setMaxDate(long)
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800506 */
507 public void setDate(long date, boolean animate, boolean center) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700508 mDelegate.setDate(date, animate, center);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800509 }
510
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700511 @Override
512 protected void onConfigurationChanged(Configuration newConfig) {
513 super.onConfigurationChanged(newConfig);
514 mDelegate.onConfigurationChanged(newConfig);
515 }
516
517 @Override
518 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
519 super.onInitializeAccessibilityEvent(event);
520 mDelegate.onInitializeAccessibilityEvent(event);
521 }
522
523 @Override
524 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
525 super.onInitializeAccessibilityNodeInfo(info);
526 mDelegate.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganovff375052012-03-01 15:57:22 -0800527 }
528
529 /**
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700530 * A delegate interface that defined the public API of the CalendarView. Allows different
531 * CalendarView implementations. This would need to be implemented by the CalendarView delegates
532 * for the real behavior.
Svetoslav Ganovff375052012-03-01 15:57:22 -0800533 */
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700534 private interface CalendarViewDelegate {
535 void setShownWeekCount(int count);
536 int getShownWeekCount();
537
538 void setSelectedWeekBackgroundColor(int color);
539 int getSelectedWeekBackgroundColor();
540
541 void setFocusedMonthDateColor(int color);
542 int getFocusedMonthDateColor();
543
544 void setUnfocusedMonthDateColor(int color);
545 int getUnfocusedMonthDateColor();
546
547 void setWeekNumberColor(int color);
548 int getWeekNumberColor();
549
550 void setWeekSeparatorLineColor(int color);
551 int getWeekSeparatorLineColor();
552
553 void setSelectedDateVerticalBar(int resourceId);
554 void setSelectedDateVerticalBar(Drawable drawable);
555 Drawable getSelectedDateVerticalBar();
556
557 void setWeekDayTextAppearance(int resourceId);
558 int getWeekDayTextAppearance();
559
560 void setDateTextAppearance(int resourceId);
561 int getDateTextAppearance();
562
563 void setEnabled(boolean enabled);
564 boolean isEnabled();
565
566 void setMinDate(long minDate);
567 long getMinDate();
568
569 void setMaxDate(long maxDate);
570 long getMaxDate();
571
572 void setShowWeekNumber(boolean showWeekNumber);
573 boolean getShowWeekNumber();
574
575 void setFirstDayOfWeek(int firstDayOfWeek);
576 int getFirstDayOfWeek();
577
578 void setDate(long date);
579 void setDate(long date, boolean animate, boolean center);
580 long getDate();
581
582 void setOnDateChangeListener(OnDateChangeListener listener);
583
584 void onConfigurationChanged(Configuration newConfig);
585 void onInitializeAccessibilityEvent(AccessibilityEvent event);
586 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
587 }
588
589 /**
590 * An abstract class which can be used as a start for CalendarView implementations
591 */
592 abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate {
593 // The delegator
594 protected CalendarView mDelegator;
595
596 // The context
597 protected Context mContext;
598
599 // The current locale
600 protected Locale mCurrentLocale;
601
602 AbstractCalendarViewDelegate(CalendarView delegator, Context context) {
603 mDelegator = delegator;
604 mContext = context;
605
606 // Initialization based on locale
607 setCurrentLocale(Locale.getDefault());
608 }
609
610 protected void setCurrentLocale(Locale locale) {
611 if (locale.equals(mCurrentLocale)) {
612 return;
613 }
614 mCurrentLocale = locale;
Svetoslav Ganovff375052012-03-01 15:57:22 -0800615 }
616 }
617
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800618 /**
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700619 * A delegate implementing the legacy CalendarView
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700620 */
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700621 private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate {
622
623 /**
624 * Default value whether to show week number.
625 */
626 private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
627
628 /**
629 * The number of milliseconds in a day.e
630 */
631 private static final long MILLIS_IN_DAY = 86400000L;
632
633 /**
634 * The number of day in a week.
635 */
636 private static final int DAYS_PER_WEEK = 7;
637
638 /**
639 * The number of milliseconds in a week.
640 */
641 private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
642
643 /**
644 * Affects when the month selection will change while scrolling upe
645 */
646 private static final int SCROLL_HYST_WEEKS = 2;
647
648 /**
649 * How long the GoTo fling animation should last.
650 */
651 private static final int GOTO_SCROLL_DURATION = 1000;
652
653 /**
654 * The duration of the adjustment upon a user scroll in milliseconds.
655 */
656 private static final int ADJUSTMENT_SCROLL_DURATION = 500;
657
658 /**
659 * How long to wait after receiving an onScrollStateChanged notification
660 * before acting on it.
661 */
662 private static final int SCROLL_CHANGE_DELAY = 40;
663
664 /**
665 * String for parsing dates.
666 */
667 private static final String DATE_FORMAT = "MM/dd/yyyy";
668
669 /**
670 * The default minimal date.
671 */
672 private static final String DEFAULT_MIN_DATE = "01/01/1900";
673
674 /**
675 * The default maximal date.
676 */
677 private static final String DEFAULT_MAX_DATE = "01/01/2100";
678
679 private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
680
681 private static final int DEFAULT_DATE_TEXT_SIZE = 14;
682
683 private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
684
685 private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
686
687 private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
688
689 private static final int UNSCALED_BOTTOM_BUFFER = 20;
690
691 private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
692
693 private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
694
695 private final int mWeekSeperatorLineWidth;
696
697 private int mDateTextSize;
698
699 private Drawable mSelectedDateVerticalBar;
700
701 private final int mSelectedDateVerticalBarWidth;
702
703 private int mSelectedWeekBackgroundColor;
704
705 private int mFocusedMonthDateColor;
706
707 private int mUnfocusedMonthDateColor;
708
709 private int mWeekSeparatorLineColor;
710
711 private int mWeekNumberColor;
712
713 private int mWeekDayTextAppearanceResId;
714
715 private int mDateTextAppearanceResId;
716
717 /**
718 * The top offset of the weeks list.
719 */
720 private int mListScrollTopOffset = 2;
721
722 /**
723 * The visible height of a week view.
724 */
725 private int mWeekMinVisibleHeight = 12;
726
727 /**
728 * The visible height of a week view.
729 */
730 private int mBottomBuffer = 20;
731
732 /**
733 * The number of shown weeks.
734 */
735 private int mShownWeekCount;
736
737 /**
738 * Flag whether to show the week number.
739 */
740 private boolean mShowWeekNumber;
741
742 /**
743 * The number of day per week to be shown.
744 */
745 private int mDaysPerWeek = 7;
746
747 /**
748 * The friction of the week list while flinging.
749 */
750 private float mFriction = .05f;
751
752 /**
753 * Scale for adjusting velocity of the week list while flinging.
754 */
755 private float mVelocityScale = 0.333f;
756
757 /**
758 * The adapter for the weeks list.
759 */
760 private WeeksAdapter mAdapter;
761
762 /**
763 * The weeks list.
764 */
765 private ListView mListView;
766
767 /**
768 * The name of the month to display.
769 */
770 private TextView mMonthName;
771
772 /**
773 * The header with week day names.
774 */
775 private ViewGroup mDayNamesHeader;
776
777 /**
778 * Cached labels for the week names header.
779 */
780 private String[] mDayLabels;
781
782 /**
783 * The first day of the week.
784 */
785 private int mFirstDayOfWeek;
786
787 /**
788 * Which month should be displayed/highlighted [0-11].
789 */
790 private int mCurrentMonthDisplayed = -1;
791
792 /**
793 * Used for tracking during a scroll.
794 */
795 private long mPreviousScrollPosition;
796
797 /**
798 * Used for tracking which direction the view is scrolling.
799 */
800 private boolean mIsScrollingUp = false;
801
802 /**
803 * The previous scroll state of the weeks ListView.
804 */
805 private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
806
807 /**
808 * The current scroll state of the weeks ListView.
809 */
810 private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
811
812 /**
813 * Listener for changes in the selected day.
814 */
815 private OnDateChangeListener mOnDateChangeListener;
816
817 /**
818 * Command for adjusting the position after a scroll/fling.
819 */
820 private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
821
822 /**
823 * Temporary instance to avoid multiple instantiations.
824 */
825 private Calendar mTempDate;
826
827 /**
828 * The first day of the focused month.
829 */
830 private Calendar mFirstDayOfMonth;
831
832 /**
833 * The start date of the range supported by this picker.
834 */
835 private Calendar mMinDate;
836
837 /**
838 * The end date of the range supported by this picker.
839 */
840 private Calendar mMaxDate;
841
842 /**
843 * Date format for parsing dates.
844 */
845 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
846
847 LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs,
848 int defStyleAttr, int defStyleRes) {
849 super(delegator, context);
850
851 // initialization based on locale
852 setCurrentLocale(Locale.getDefault());
853
854 TypedArray attributesArray = context.obtainStyledAttributes(attrs,
855 R.styleable.CalendarView, defStyleAttr, defStyleRes);
856 mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
857 DEFAULT_SHOW_WEEK_NUMBER);
858 mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
859 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
860 String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
861 if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
862 parseDate(DEFAULT_MIN_DATE, mMinDate);
863 }
864 String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
865 if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
866 parseDate(DEFAULT_MAX_DATE, mMaxDate);
867 }
868 if (mMaxDate.before(mMinDate)) {
869 throw new IllegalArgumentException("Max date cannot be before min date.");
870 }
871 mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
872 DEFAULT_SHOWN_WEEK_COUNT);
873 mSelectedWeekBackgroundColor = attributesArray.getColor(
874 R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
875 mFocusedMonthDateColor = attributesArray.getColor(
876 R.styleable.CalendarView_focusedMonthDateColor, 0);
877 mUnfocusedMonthDateColor = attributesArray.getColor(
878 R.styleable.CalendarView_unfocusedMonthDateColor, 0);
879 mWeekSeparatorLineColor = attributesArray.getColor(
880 R.styleable.CalendarView_weekSeparatorLineColor, 0);
881 mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
882 mSelectedDateVerticalBar = attributesArray.getDrawable(
883 R.styleable.CalendarView_selectedDateVerticalBar);
884
885 mDateTextAppearanceResId = attributesArray.getResourceId(
886 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
887 updateDateTextSize();
888
889 mWeekDayTextAppearanceResId = attributesArray.getResourceId(
890 R.styleable.CalendarView_weekDayTextAppearance,
891 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
892 attributesArray.recycle();
893
894 DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
895 mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
896 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
897 mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
898 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
899 mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
900 UNSCALED_BOTTOM_BUFFER, displayMetrics);
901 mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
902 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
903 mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
904 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
905
906 LayoutInflater layoutInflater = (LayoutInflater) mContext
907 .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
908 View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
909 mDelegator.addView(content);
910
911 mListView = (ListView) mDelegator.findViewById(R.id.list);
912 mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
913 mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
914
915 setUpHeader();
916 setUpListView();
917 setUpAdapter();
918
919 // go to today or whichever is close to today min or max date
920 mTempDate.setTimeInMillis(System.currentTimeMillis());
921 if (mTempDate.before(mMinDate)) {
922 goTo(mMinDate, false, true, true);
923 } else if (mMaxDate.before(mTempDate)) {
924 goTo(mMaxDate, false, true, true);
925 } else {
926 goTo(mTempDate, false, true, true);
927 }
928
929 mDelegator.invalidate();
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700930 }
931
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700932 @Override
933 public void setShownWeekCount(int count) {
934 if (mShownWeekCount != count) {
935 mShownWeekCount = count;
936 mDelegator.invalidate();
937 }
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700938 }
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700939
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700940 @Override
941 public int getShownWeekCount() {
942 return mShownWeekCount;
943 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800944
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700945 @Override
946 public void setSelectedWeekBackgroundColor(int color) {
947 if (mSelectedWeekBackgroundColor != color) {
948 mSelectedWeekBackgroundColor = color;
949 final int childCount = mListView.getChildCount();
950 for (int i = 0; i < childCount; i++) {
951 WeekView weekView = (WeekView) mListView.getChildAt(i);
952 if (weekView.mHasSelectedDay) {
953 weekView.invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800954 }
955 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800956 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -0700957 }
958
959 @Override
960 public int getSelectedWeekBackgroundColor() {
961 return mSelectedWeekBackgroundColor;
962 }
963
964 @Override
965 public void setFocusedMonthDateColor(int color) {
966 if (mFocusedMonthDateColor != color) {
967 mFocusedMonthDateColor = color;
968 final int childCount = mListView.getChildCount();
969 for (int i = 0; i < childCount; i++) {
970 WeekView weekView = (WeekView) mListView.getChildAt(i);
971 if (weekView.mHasFocusedDay) {
972 weekView.invalidate();
973 }
974 }
975 }
976 }
977
978 @Override
979 public int getFocusedMonthDateColor() {
980 return mFocusedMonthDateColor;
981 }
982
983 @Override
984 public void setUnfocusedMonthDateColor(int color) {
985 if (mUnfocusedMonthDateColor != color) {
986 mUnfocusedMonthDateColor = color;
987 final int childCount = mListView.getChildCount();
988 for (int i = 0; i < childCount; i++) {
989 WeekView weekView = (WeekView) mListView.getChildAt(i);
990 if (weekView.mHasUnfocusedDay) {
991 weekView.invalidate();
992 }
993 }
994 }
995 }
996
997 @Override
998 public int getUnfocusedMonthDateColor() {
999 return mFocusedMonthDateColor;
1000 }
1001
1002 @Override
1003 public void setWeekNumberColor(int color) {
1004 if (mWeekNumberColor != color) {
1005 mWeekNumberColor = color;
1006 if (mShowWeekNumber) {
1007 invalidateAllWeekViews();
1008 }
1009 }
1010 }
1011
1012 @Override
1013 public int getWeekNumberColor() {
1014 return mWeekNumberColor;
1015 }
1016
1017 @Override
1018 public void setWeekSeparatorLineColor(int color) {
1019 if (mWeekSeparatorLineColor != color) {
1020 mWeekSeparatorLineColor = color;
1021 invalidateAllWeekViews();
1022 }
1023 }
1024
1025 @Override
1026 public int getWeekSeparatorLineColor() {
1027 return mWeekSeparatorLineColor;
1028 }
1029
1030 @Override
1031 public void setSelectedDateVerticalBar(int resourceId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001032 Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001033 setSelectedDateVerticalBar(drawable);
1034 }
1035
1036 @Override
1037 public void setSelectedDateVerticalBar(Drawable drawable) {
1038 if (mSelectedDateVerticalBar != drawable) {
1039 mSelectedDateVerticalBar = drawable;
1040 final int childCount = mListView.getChildCount();
1041 for (int i = 0; i < childCount; i++) {
1042 WeekView weekView = (WeekView) mListView.getChildAt(i);
1043 if (weekView.mHasSelectedDay) {
1044 weekView.invalidate();
1045 }
1046 }
1047 }
1048 }
1049
1050 @Override
1051 public Drawable getSelectedDateVerticalBar() {
1052 return mSelectedDateVerticalBar;
1053 }
1054
1055 @Override
1056 public void setWeekDayTextAppearance(int resourceId) {
1057 if (mWeekDayTextAppearanceResId != resourceId) {
1058 mWeekDayTextAppearanceResId = resourceId;
1059 setUpHeader();
1060 }
1061 }
1062
1063 @Override
1064 public int getWeekDayTextAppearance() {
1065 return mWeekDayTextAppearanceResId;
1066 }
1067
1068 @Override
1069 public void setDateTextAppearance(int resourceId) {
1070 if (mDateTextAppearanceResId != resourceId) {
1071 mDateTextAppearanceResId = resourceId;
1072 updateDateTextSize();
1073 invalidateAllWeekViews();
1074 }
1075 }
1076
1077 @Override
1078 public int getDateTextAppearance() {
1079 return mDateTextAppearanceResId;
1080 }
1081
1082 @Override
1083 public void setEnabled(boolean enabled) {
1084 mListView.setEnabled(enabled);
1085 }
1086
1087 @Override
1088 public boolean isEnabled() {
1089 return mListView.isEnabled();
1090 }
1091
1092 @Override
1093 public void setMinDate(long minDate) {
1094 mTempDate.setTimeInMillis(minDate);
1095 if (isSameDate(mTempDate, mMinDate)) {
1096 return;
1097 }
1098 mMinDate.setTimeInMillis(minDate);
1099 // make sure the current date is not earlier than
1100 // the new min date since the latter is used for
1101 // calculating the indices in the adapter thus
1102 // avoiding out of bounds error
1103 Calendar date = mAdapter.mSelectedDate;
1104 if (date.before(mMinDate)) {
1105 mAdapter.setSelectedDay(mMinDate);
1106 }
1107 // reinitialize the adapter since its range depends on min date
1108 mAdapter.init();
1109 if (date.before(mMinDate)) {
1110 setDate(mTempDate.getTimeInMillis());
1111 } else {
1112 // we go to the current date to force the ListView to query its
1113 // adapter for the shown views since we have changed the adapter
1114 // range and the base from which the later calculates item indices
1115 // note that calling setDate will not work since the date is the same
1116 goTo(date, false, true, false);
1117 }
1118 }
1119
1120 @Override
1121 public long getMinDate() {
1122 return mMinDate.getTimeInMillis();
1123 }
1124
1125 @Override
1126 public void setMaxDate(long maxDate) {
1127 mTempDate.setTimeInMillis(maxDate);
1128 if (isSameDate(mTempDate, mMaxDate)) {
1129 return;
1130 }
1131 mMaxDate.setTimeInMillis(maxDate);
1132 // reinitialize the adapter since its range depends on max date
1133 mAdapter.init();
1134 Calendar date = mAdapter.mSelectedDate;
1135 if (date.after(mMaxDate)) {
1136 setDate(mMaxDate.getTimeInMillis());
1137 } else {
1138 // we go to the current date to force the ListView to query its
1139 // adapter for the shown views since we have changed the adapter
1140 // range and the base from which the later calculates item indices
1141 // note that calling setDate will not work since the date is the same
1142 goTo(date, false, true, false);
1143 }
1144 }
1145
1146 @Override
1147 public long getMaxDate() {
1148 return mMaxDate.getTimeInMillis();
1149 }
1150
1151 @Override
1152 public void setShowWeekNumber(boolean showWeekNumber) {
1153 if (mShowWeekNumber == showWeekNumber) {
1154 return;
1155 }
1156 mShowWeekNumber = showWeekNumber;
1157 mAdapter.notifyDataSetChanged();
1158 setUpHeader();
1159 }
1160
1161 @Override
1162 public boolean getShowWeekNumber() {
1163 return mShowWeekNumber;
1164 }
1165
1166 @Override
1167 public void setFirstDayOfWeek(int firstDayOfWeek) {
1168 if (mFirstDayOfWeek == firstDayOfWeek) {
1169 return;
1170 }
1171 mFirstDayOfWeek = firstDayOfWeek;
1172 mAdapter.init();
1173 mAdapter.notifyDataSetChanged();
1174 setUpHeader();
1175 }
1176
1177 @Override
1178 public int getFirstDayOfWeek() {
1179 return mFirstDayOfWeek;
1180 }
1181
1182 @Override
1183 public void setDate(long date) {
1184 setDate(date, false, false);
1185 }
1186
1187 @Override
1188 public void setDate(long date, boolean animate, boolean center) {
1189 mTempDate.setTimeInMillis(date);
1190 if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
1191 return;
1192 }
1193 goTo(mTempDate, animate, true, center);
1194 }
1195
1196 @Override
1197 public long getDate() {
1198 return mAdapter.mSelectedDate.getTimeInMillis();
1199 }
1200
1201 @Override
1202 public void setOnDateChangeListener(OnDateChangeListener listener) {
1203 mOnDateChangeListener = listener;
1204 }
1205
1206 @Override
1207 public void onConfigurationChanged(Configuration newConfig) {
1208 setCurrentLocale(newConfig.locale);
1209 }
1210
1211 @Override
1212 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1213 event.setClassName(CalendarView.class.getName());
1214 }
1215
1216 @Override
1217 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1218 info.setClassName(CalendarView.class.getName());
1219 }
1220
1221 /**
1222 * Sets the current locale.
1223 *
1224 * @param locale The current locale.
1225 */
1226 @Override
1227 protected void setCurrentLocale(Locale locale) {
1228 super.setCurrentLocale(locale);
1229
1230 mTempDate = getCalendarForLocale(mTempDate, locale);
1231 mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
1232 mMinDate = getCalendarForLocale(mMinDate, locale);
1233 mMaxDate = getCalendarForLocale(mMaxDate, locale);
1234 }
1235 private void updateDateTextSize() {
1236 TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
1237 mDateTextAppearanceResId, R.styleable.TextAppearance);
1238 mDateTextSize = dateTextAppearance.getDimensionPixelSize(
1239 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
1240 dateTextAppearance.recycle();
1241 }
1242
1243 /**
1244 * Invalidates all week views.
1245 */
1246 private void invalidateAllWeekViews() {
1247 final int childCount = mListView.getChildCount();
1248 for (int i = 0; i < childCount; i++) {
1249 View view = mListView.getChildAt(i);
1250 view.invalidate();
1251 }
1252 }
1253
1254 /**
1255 * Gets a calendar for locale bootstrapped with the value of a given calendar.
1256 *
1257 * @param oldCalendar The old calendar.
1258 * @param locale The locale.
1259 */
1260 private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
1261 if (oldCalendar == null) {
1262 return Calendar.getInstance(locale);
1263 } else {
1264 final long currentTimeMillis = oldCalendar.getTimeInMillis();
1265 Calendar newCalendar = Calendar.getInstance(locale);
1266 newCalendar.setTimeInMillis(currentTimeMillis);
1267 return newCalendar;
1268 }
1269 }
1270
1271 /**
1272 * @return True if the <code>firstDate</code> is the same as the <code>
1273 * secondDate</code>.
1274 */
1275 private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
1276 return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
1277 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
1278 }
1279
1280 /**
1281 * Creates a new adapter if necessary and sets up its parameters.
1282 */
1283 private void setUpAdapter() {
1284 if (mAdapter == null) {
1285 mAdapter = new WeeksAdapter(mContext);
1286 mAdapter.registerDataSetObserver(new DataSetObserver() {
1287 @Override
1288 public void onChanged() {
1289 if (mOnDateChangeListener != null) {
1290 Calendar selectedDay = mAdapter.getSelectedDay();
1291 mOnDateChangeListener.onSelectedDayChange(mDelegator,
1292 selectedDay.get(Calendar.YEAR),
1293 selectedDay.get(Calendar.MONTH),
1294 selectedDay.get(Calendar.DAY_OF_MONTH));
1295 }
1296 }
1297 });
1298 mListView.setAdapter(mAdapter);
1299 }
1300
1301 // refresh the view with the new parameters
1302 mAdapter.notifyDataSetChanged();
1303 }
1304
1305 /**
1306 * Sets up the strings to be used by the header.
1307 */
1308 private void setUpHeader() {
1309 mDayLabels = new String[mDaysPerWeek];
1310 for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
1311 int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
1312 mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
1313 DateUtils.LENGTH_SHORTEST);
1314 }
1315
1316 TextView label = (TextView) mDayNamesHeader.getChildAt(0);
1317 if (mShowWeekNumber) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001318 label.setVisibility(View.VISIBLE);
1319 } else {
1320 label.setVisibility(View.GONE);
1321 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001322 for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
1323 label = (TextView) mDayNamesHeader.getChildAt(i);
1324 if (mWeekDayTextAppearanceResId > -1) {
1325 label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
1326 }
1327 if (i < mDaysPerWeek + 1) {
1328 label.setText(mDayLabels[i - 1]);
1329 label.setVisibility(View.VISIBLE);
1330 } else {
1331 label.setVisibility(View.GONE);
1332 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001333 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001334 mDayNamesHeader.invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001335 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001336
1337 /**
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001338 * Sets all the required fields for the list view.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001339 */
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001340 private void setUpListView() {
1341 // Configure the listview
1342 mListView.setDivider(null);
1343 mListView.setItemsCanFocus(true);
1344 mListView.setVerticalScrollBarEnabled(false);
1345 mListView.setOnScrollListener(new OnScrollListener() {
1346 public void onScrollStateChanged(AbsListView view, int scrollState) {
1347 LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState);
1348 }
1349
1350 public void onScroll(
1351 AbsListView view, int firstVisibleItem, int visibleItemCount,
1352 int totalItemCount) {
1353 LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem,
1354 visibleItemCount, totalItemCount);
1355 }
1356 });
1357 // Make the scrolling behavior nicer
1358 mListView.setFriction(mFriction);
1359 mListView.setVelocityScale(mVelocityScale);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001360 }
1361
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001362 /**
1363 * This moves to the specified time in the view. If the time is not already
1364 * in range it will move the list so that the first of the month containing
1365 * the time is at the top of the view. If the new time is already in view
1366 * the list will not be scrolled unless forceScroll is true. This time may
1367 * optionally be highlighted as selected as well.
1368 *
1369 * @param date The time to move to.
1370 * @param animate Whether to scroll to the given time or just redraw at the
1371 * new location.
1372 * @param setSelected Whether to set the given time as selected.
1373 * @param forceScroll Whether to recenter even if the time is already
1374 * visible.
1375 *
1376 * @throws IllegalArgumentException of the provided date is before the
1377 * range start of after the range end.
1378 */
1379 private void goTo(Calendar date, boolean animate, boolean setSelected,
1380 boolean forceScroll) {
1381 if (date.before(mMinDate) || date.after(mMaxDate)) {
1382 throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
1383 + " and " + mMaxDate.getTime());
1384 }
1385 // Find the first and last entirely visible weeks
1386 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
1387 View firstChild = mListView.getChildAt(0);
1388 if (firstChild != null && firstChild.getTop() < 0) {
1389 firstFullyVisiblePosition++;
1390 }
1391 int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
1392 if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
1393 lastFullyVisiblePosition--;
1394 }
1395 if (setSelected) {
1396 mAdapter.setSelectedDay(date);
1397 }
1398 // Get the week we're going to
1399 int position = getWeeksSinceMinDate(date);
1400
1401 // Check if the selected day is now outside of our visible range
1402 // and if so scroll to the month that contains it
1403 if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
1404 || forceScroll) {
1405 mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
1406 mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
1407
1408 setMonthDisplayed(mFirstDayOfMonth);
1409
1410 // the earliest time we can scroll to is the min date
1411 if (mFirstDayOfMonth.before(mMinDate)) {
1412 position = 0;
1413 } else {
1414 position = getWeeksSinceMinDate(mFirstDayOfMonth);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001415 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001416
1417 mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
1418 if (animate) {
1419 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
1420 GOTO_SCROLL_DURATION);
1421 } else {
1422 mListView.setSelectionFromTop(position, mListScrollTopOffset);
1423 // Perform any after scroll operations that are needed
1424 onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
1425 }
1426 } else if (setSelected) {
1427 // Otherwise just set the selection
1428 setMonthDisplayed(date);
1429 }
1430 }
1431
1432 /**
1433 * Parses the given <code>date</code> and in case of success sets
1434 * the result to the <code>outDate</code>.
1435 *
1436 * @return True if the date was parsed.
1437 */
1438 private boolean parseDate(String date, Calendar outDate) {
1439 try {
1440 outDate.setTime(mDateFormat.parse(date));
1441 return true;
1442 } catch (ParseException e) {
1443 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
1444 return false;
1445 }
1446 }
1447
1448 /**
1449 * Called when a <code>view</code> transitions to a new <code>scrollState
1450 * </code>.
1451 */
1452 private void onScrollStateChanged(AbsListView view, int scrollState) {
1453 mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
1454 }
1455
1456 /**
1457 * Updates the title and selected month if the <code>view</code> has moved to a new
1458 * month.
1459 */
1460 private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1461 int totalItemCount) {
1462 WeekView child = (WeekView) view.getChildAt(0);
1463 if (child == null) {
1464 return;
1465 }
1466
1467 // Figure out where we are
1468 long currScroll =
1469 view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
1470
1471 // If we have moved since our last call update the direction
1472 if (currScroll < mPreviousScrollPosition) {
1473 mIsScrollingUp = true;
1474 } else if (currScroll > mPreviousScrollPosition) {
1475 mIsScrollingUp = false;
1476 } else {
1477 return;
1478 }
1479
1480 // Use some hysteresis for checking which month to highlight. This
1481 // causes the month to transition when two full weeks of a month are
1482 // visible when scrolling up, and when the first day in a month reaches
1483 // the top of the screen when scrolling down.
1484 int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
1485 if (mIsScrollingUp) {
1486 child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
1487 } else if (offset != 0) {
1488 child = (WeekView) view.getChildAt(offset);
1489 }
1490
Fabrice Di Meglio83ed4fe2014-01-28 15:11:49 -08001491 if (child != null) {
1492 // Find out which month we're moving into
1493 int month;
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001494 if (mIsScrollingUp) {
Fabrice Di Meglio83ed4fe2014-01-28 15:11:49 -08001495 month = child.getMonthOfFirstWeekDay();
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001496 } else {
Fabrice Di Meglio83ed4fe2014-01-28 15:11:49 -08001497 month = child.getMonthOfLastWeekDay();
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001498 }
Fabrice Di Meglio83ed4fe2014-01-28 15:11:49 -08001499
1500 // And how it relates to our current highlighted month
1501 int monthDiff;
1502 if (mCurrentMonthDisplayed == 11 && month == 0) {
1503 monthDiff = 1;
1504 } else if (mCurrentMonthDisplayed == 0 && month == 11) {
1505 monthDiff = -1;
1506 } else {
1507 monthDiff = month - mCurrentMonthDisplayed;
1508 }
1509
1510 // Only switch months if we're scrolling away from the currently
1511 // selected month
1512 if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
1513 Calendar firstDay = child.getFirstDay();
1514 if (mIsScrollingUp) {
1515 firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
1516 } else {
1517 firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
1518 }
1519 setMonthDisplayed(firstDay);
1520 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001521 }
1522 mPreviousScrollPosition = currScroll;
1523 mPreviousScrollState = mCurrentScrollState;
1524 }
1525
1526 /**
1527 * Sets the month displayed at the top of this view based on time. Override
1528 * to add custom events when the title is changed.
1529 *
1530 * @param calendar A day in the new focus month.
1531 */
1532 private void setMonthDisplayed(Calendar calendar) {
1533 mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
1534 mAdapter.setFocusMonth(mCurrentMonthDisplayed);
1535 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
1536 | DateUtils.FORMAT_SHOW_YEAR;
1537 final long millis = calendar.getTimeInMillis();
1538 String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
1539 mMonthName.setText(newMonthName);
1540 mMonthName.invalidate();
1541 }
1542
1543 /**
1544 * @return Returns the number of weeks between the current <code>date</code>
1545 * and the <code>mMinDate</code>.
1546 */
1547 private int getWeeksSinceMinDate(Calendar date) {
1548 if (date.before(mMinDate)) {
1549 throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
1550 + " does not precede toDate: " + date.getTime());
1551 }
1552 long endTimeMillis = date.getTimeInMillis()
1553 + date.getTimeZone().getOffset(date.getTimeInMillis());
1554 long startTimeMillis = mMinDate.getTimeInMillis()
1555 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
1556 long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
1557 * MILLIS_IN_DAY;
1558 return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
1559 }
1560
1561 /**
1562 * Command responsible for acting upon scroll state changes.
1563 */
1564 private class ScrollStateRunnable implements Runnable {
1565 private AbsListView mView;
1566
1567 private int mNewState;
1568
1569 /**
1570 * Sets up the runnable with a short delay in case the scroll state
1571 * immediately changes again.
1572 *
1573 * @param view The list view that changed state
1574 * @param scrollState The new state it changed to
1575 */
1576 public void doScrollStateChange(AbsListView view, int scrollState) {
1577 mView = view;
1578 mNewState = scrollState;
1579 mDelegator.removeCallbacks(this);
1580 mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
1581 }
1582
1583 public void run() {
1584 mCurrentScrollState = mNewState;
1585 // Fix the position after a scroll or a fling ends
1586 if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
1587 && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
1588 View child = mView.getChildAt(0);
1589 if (child == null) {
1590 // The view is no longer visible, just return
1591 return;
1592 }
1593 int dist = child.getBottom() - mListScrollTopOffset;
1594 if (dist > mListScrollTopOffset) {
1595 if (mIsScrollingUp) {
1596 mView.smoothScrollBy(dist - child.getHeight(),
1597 ADJUSTMENT_SCROLL_DURATION);
1598 } else {
1599 mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
1600 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001601 }
1602 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001603 mPreviousScrollState = mNewState;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001604 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001605 }
1606
1607 /**
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001608 * <p>
1609 * This is a specialized adapter for creating a list of weeks with
1610 * selectable days. It can be configured to display the week number, start
1611 * the week on a given day, show a reduced number of days, or display an
1612 * arbitrary number of weeks at a time.
1613 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001614 */
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001615 private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001616
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001617 private int mSelectedWeek;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001618
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001619 private GestureDetector mGestureDetector;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001620
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001621 private int mFocusedMonth;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001622
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001623 private final Calendar mSelectedDate = Calendar.getInstance();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001624
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001625 private int mTotalWeekCount;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001626
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001627 public WeeksAdapter(Context context) {
1628 mContext = context;
1629 mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
1630 init();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001631 }
1632
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001633 /**
1634 * Set up the gesture detector and selected time
1635 */
1636 private void init() {
1637 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1638 mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
1639 if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
1640 || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
1641 mTotalWeekCount++;
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001642 }
Fabrice Di Meglioba4d2a02014-01-28 14:55:17 -08001643 notifyDataSetChanged();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001644 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001645
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001646 /**
1647 * Updates the selected day and related parameters.
1648 *
1649 * @param selectedDay The time to highlight
1650 */
1651 public void setSelectedDay(Calendar selectedDay) {
1652 if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
1653 && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
1654 return;
1655 }
1656 mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
1657 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1658 mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
1659 notifyDataSetChanged();
1660 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001661
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001662 /**
1663 * @return The selected day of month.
1664 */
1665 public Calendar getSelectedDay() {
1666 return mSelectedDate;
1667 }
1668
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001669 @Override
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001670 public int getCount() {
1671 return mTotalWeekCount;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001672 }
1673
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001674 @Override
1675 public Object getItem(int position) {
1676 return null;
1677 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001678
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001679 @Override
1680 public long getItemId(int position) {
1681 return position;
1682 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001683
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001684 @Override
1685 public View getView(int position, View convertView, ViewGroup parent) {
1686 WeekView weekView = null;
1687 if (convertView != null) {
1688 weekView = (WeekView) convertView;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001689 } else {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001690 weekView = new WeekView(mContext);
1691 android.widget.AbsListView.LayoutParams params =
1692 new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
1693 LayoutParams.WRAP_CONTENT);
1694 weekView.setLayoutParams(params);
1695 weekView.setClickable(true);
1696 weekView.setOnTouchListener(this);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001697 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001698
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001699 int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1700 Calendar.DAY_OF_WEEK) : -1;
1701 weekView.init(position, selectedWeekDay, mFocusedMonth);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001702
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001703 return weekView;
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001704 }
1705
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001706 /**
1707 * Changes which month is in focus and updates the view.
1708 *
1709 * @param month The month to show as in focus [0-11]
1710 */
1711 public void setFocusMonth(int month) {
1712 if (mFocusedMonth == month) {
1713 return;
1714 }
1715 mFocusedMonth = month;
1716 notifyDataSetChanged();
1717 }
1718
1719 @Override
1720 public boolean onTouch(View v, MotionEvent event) {
1721 if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
1722 WeekView weekView = (WeekView) v;
1723 // if we cannot find a day for the given location we are done
1724 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
1725 return true;
1726 }
1727 // it is possible that the touched day is outside the valid range
1728 // we draw whole weeks but range end can fall not on the week end
1729 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1730 return true;
1731 }
1732 onDateTapped(mTempDate);
1733 return true;
1734 }
Svetoslav Ganov1a730dc2011-03-14 18:30:39 -07001735 return false;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001736 }
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001737
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001738 /**
1739 * Maintains the same hour/min/sec but moves the day to the tapped day.
1740 *
1741 * @param day The day that was tapped
1742 */
1743 private void onDateTapped(Calendar day) {
1744 setSelectedDay(day);
1745 setMonthDisplayed(day);
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001746 }
1747
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001748 /**
1749 * This is here so we can identify single tap events and set the
1750 * selected day correctly
1751 */
1752 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
1753 @Override
1754 public boolean onSingleTapUp(MotionEvent e) {
1755 return true;
1756 }
1757 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001758 }
1759
1760 /**
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001761 * <p>
1762 * This is a dynamic view for drawing a single week. It can be configured to
1763 * display the week number, start the week on a given day, or show a reduced
1764 * number of days. It is intended for use as a single view within a
1765 * ListView. See {@link WeeksAdapter} for usage.
1766 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001767 */
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001768 private class WeekView extends View {
1769
1770 private final Rect mTempRect = new Rect();
1771
1772 private final Paint mDrawPaint = new Paint();
1773
1774 private final Paint mMonthNumDrawPaint = new Paint();
1775
1776 // Cache the number strings so we don't have to recompute them each time
1777 private String[] mDayNumbers;
1778
1779 // Quick lookup for checking which days are in the focus month
1780 private boolean[] mFocusDay;
1781
1782 // Whether this view has a focused day.
1783 private boolean mHasFocusedDay;
1784
1785 // Whether this view has only focused days.
1786 private boolean mHasUnfocusedDay;
1787
1788 // The first day displayed by this item
1789 private Calendar mFirstDay;
1790
1791 // The month of the first day in this week
1792 private int mMonthOfFirstWeekDay = -1;
1793
1794 // The month of the last day in this week
1795 private int mLastWeekDayMonth = -1;
1796
1797 // The position of this week, equivalent to weeks since the week of Jan
1798 // 1st, 1900
1799 private int mWeek = -1;
1800
1801 // Quick reference to the width of this view, matches parent
1802 private int mWidth;
1803
1804 // The height this view should draw at in pixels, set by height param
1805 private int mHeight;
1806
1807 // If this view contains the selected day
1808 private boolean mHasSelectedDay = false;
1809
1810 // Which day is selected [0-6] or -1 if no day is selected
1811 private int mSelectedDay = -1;
1812
1813 // The number of days + a spot for week number if it is displayed
1814 private int mNumCells;
1815
1816 // The left edge of the selected day
1817 private int mSelectedLeft = -1;
1818
1819 // The right edge of the selected day
1820 private int mSelectedRight = -1;
1821
1822 public WeekView(Context context) {
1823 super(context);
1824
1825 // Sets up any standard paints that will be used
1826 initilaizePaints();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001827 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001828
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001829 /**
1830 * Initializes this week view.
1831 *
1832 * @param weekNumber The number of the week this view represents. The
1833 * week number is a zero based index of the weeks since
1834 * {@link CalendarView#getMinDate()}.
1835 * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
1836 * selected day.
1837 * @param focusedMonth The month that is currently in focus i.e.
1838 * highlighted.
1839 */
1840 public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
1841 mSelectedDay = selectedWeekDay;
1842 mHasSelectedDay = mSelectedDay != -1;
1843 mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
1844 mWeek = weekNumber;
1845 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001846
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001847 mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1848 mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001849
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001850 // Allocate space for caching the day numbers and focus values
1851 mDayNumbers = new String[mNumCells];
1852 mFocusDay = new boolean[mNumCells];
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001853
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001854 // If we're showing the week number calculate it based on Monday
1855 int i = 0;
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001856 if (mShowWeekNumber) {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001857 mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1858 mTempDate.get(Calendar.WEEK_OF_YEAR));
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001859 i++;
1860 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001861
1862 // Now adjust our starting day based on the start day of the week
1863 int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
1864 mTempDate.add(Calendar.DAY_OF_MONTH, diff);
1865
1866 mFirstDay = (Calendar) mTempDate.clone();
1867 mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
1868
1869 mHasUnfocusedDay = true;
1870 for (; i < mNumCells; i++) {
1871 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
1872 mFocusDay[i] = isFocusedDay;
1873 mHasFocusedDay |= isFocusedDay;
1874 mHasUnfocusedDay &= !isFocusedDay;
1875 // do not draw dates outside the valid range to avoid user confusion
1876 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1877 mDayNumbers[i] = "";
1878 } else {
1879 mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
1880 mTempDate.get(Calendar.DAY_OF_MONTH));
1881 }
1882 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001883 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001884 // We do one extra add at the end of the loop, if that pushed us to
1885 // new month undo it
1886 if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
1887 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
1888 }
1889 mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001890
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001891 updateSelectionPositions();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001892 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001893
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001894 /**
1895 * Initialize the paint instances.
1896 */
1897 private void initilaizePaints() {
1898 mDrawPaint.setFakeBoldText(false);
1899 mDrawPaint.setAntiAlias(true);
1900 mDrawPaint.setStyle(Style.FILL);
1901
1902 mMonthNumDrawPaint.setFakeBoldText(true);
1903 mMonthNumDrawPaint.setAntiAlias(true);
1904 mMonthNumDrawPaint.setStyle(Style.FILL);
1905 mMonthNumDrawPaint.setTextAlign(Align.CENTER);
1906 mMonthNumDrawPaint.setTextSize(mDateTextSize);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001907 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001908
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001909 /**
1910 * Returns the month of the first day in this week.
1911 *
1912 * @return The month the first day of this view is in.
1913 */
1914 public int getMonthOfFirstWeekDay() {
1915 return mMonthOfFirstWeekDay;
1916 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001917
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001918 /**
1919 * Returns the month of the last day in this week
1920 *
1921 * @return The month the last day of this view is in
1922 */
1923 public int getMonthOfLastWeekDay() {
1924 return mLastWeekDayMonth;
1925 }
1926
1927 /**
1928 * Returns the first day in this view.
1929 *
1930 * @return The first day in the view.
1931 */
1932 public Calendar getFirstDay() {
1933 return mFirstDay;
1934 }
1935
1936 /**
1937 * Calculates the day that the given x position is in, accounting for
1938 * week number.
1939 *
1940 * @param x The x position of the touch event.
1941 * @return True if a day was found for the given location.
1942 */
1943 public boolean getDayFromLocation(float x, Calendar outCalendar) {
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001944 final boolean isLayoutRtl = isLayoutRtl();
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001945
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001946 int start;
1947 int end;
1948
1949 if (isLayoutRtl) {
1950 start = 0;
1951 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001952 } else {
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001953 start = mShowWeekNumber ? mWidth / mNumCells : 0;
1954 end = mWidth;
Fabrice Di Meglio550ea7f2012-09-21 17:58:41 -07001955 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07001956
1957 if (x < start || x > end) {
1958 outCalendar.clear();
1959 return false;
1960 }
1961
1962 // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
1963 int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
1964
1965 if (isLayoutRtl) {
1966 dayPosition = mDaysPerWeek - 1 - dayPosition;
1967 }
1968
1969 outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
1970 outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
1971
1972 return true;
1973 }
1974
1975 @Override
1976 protected void onDraw(Canvas canvas) {
1977 drawBackground(canvas);
1978 drawWeekNumbersAndDates(canvas);
1979 drawWeekSeparators(canvas);
1980 drawSelectedDateVerticalBars(canvas);
1981 }
1982
1983 /**
1984 * This draws the selection highlight if a day is selected in this week.
1985 *
1986 * @param canvas The canvas to draw on
1987 */
1988 private void drawBackground(Canvas canvas) {
1989 if (!mHasSelectedDay) {
1990 return;
1991 }
1992 mDrawPaint.setColor(mSelectedWeekBackgroundColor);
1993
1994 mTempRect.top = mWeekSeperatorLineWidth;
1995 mTempRect.bottom = mHeight;
1996
1997 final boolean isLayoutRtl = isLayoutRtl();
1998
1999 if (isLayoutRtl) {
2000 mTempRect.left = 0;
2001 mTempRect.right = mSelectedLeft - 2;
2002 } else {
2003 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
2004 mTempRect.right = mSelectedLeft - 2;
2005 }
2006 canvas.drawRect(mTempRect, mDrawPaint);
2007
2008 if (isLayoutRtl) {
2009 mTempRect.left = mSelectedRight + 3;
2010 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2011 } else {
2012 mTempRect.left = mSelectedRight + 3;
2013 mTempRect.right = mWidth;
2014 }
2015 canvas.drawRect(mTempRect, mDrawPaint);
2016 }
2017
2018 /**
2019 * Draws the week and month day numbers for this week.
2020 *
2021 * @param canvas The canvas to draw on
2022 */
2023 private void drawWeekNumbersAndDates(Canvas canvas) {
2024 final float textHeight = mDrawPaint.getTextSize();
2025 final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
2026 final int nDays = mNumCells;
2027 final int divisor = 2 * nDays;
2028
2029 mDrawPaint.setTextAlign(Align.CENTER);
2030 mDrawPaint.setTextSize(mDateTextSize);
2031
2032 int i = 0;
2033
2034 if (isLayoutRtl()) {
2035 for (; i < nDays - 1; i++) {
2036 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2037 : mUnfocusedMonthDateColor);
2038 int x = (2 * i + 1) * mWidth / divisor;
2039 canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
2040 }
2041 if (mShowWeekNumber) {
2042 mDrawPaint.setColor(mWeekNumberColor);
2043 int x = mWidth - mWidth / divisor;
2044 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2045 }
2046 } else {
2047 if (mShowWeekNumber) {
2048 mDrawPaint.setColor(mWeekNumberColor);
2049 int x = mWidth / divisor;
2050 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2051 i++;
2052 }
2053 for (; i < nDays; i++) {
2054 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2055 : mUnfocusedMonthDateColor);
2056 int x = (2 * i + 1) * mWidth / divisor;
2057 canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
2058 }
2059 }
2060 }
2061
2062 /**
2063 * Draws a horizontal line for separating the weeks.
2064 *
2065 * @param canvas The canvas to draw on.
2066 */
2067 private void drawWeekSeparators(Canvas canvas) {
2068 // If it is the topmost fully visible child do not draw separator line
2069 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
2070 if (mListView.getChildAt(0).getTop() < 0) {
2071 firstFullyVisiblePosition++;
2072 }
2073 if (firstFullyVisiblePosition == mWeek) {
2074 return;
2075 }
2076 mDrawPaint.setColor(mWeekSeparatorLineColor);
2077 mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
2078 float startX;
2079 float stopX;
2080 if (isLayoutRtl()) {
2081 startX = 0;
2082 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2083 } else {
2084 startX = mShowWeekNumber ? mWidth / mNumCells : 0;
2085 stopX = mWidth;
2086 }
2087 canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
2088 }
2089
2090 /**
2091 * Draws the selected date bars if this week has a selected day.
2092 *
2093 * @param canvas The canvas to draw on
2094 */
2095 private void drawSelectedDateVerticalBars(Canvas canvas) {
2096 if (!mHasSelectedDay) {
2097 return;
2098 }
2099 mSelectedDateVerticalBar.setBounds(
2100 mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
2101 mWeekSeperatorLineWidth,
2102 mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
2103 mHeight);
2104 mSelectedDateVerticalBar.draw(canvas);
2105 mSelectedDateVerticalBar.setBounds(
2106 mSelectedRight - mSelectedDateVerticalBarWidth / 2,
2107 mWeekSeperatorLineWidth,
2108 mSelectedRight + mSelectedDateVerticalBarWidth / 2,
2109 mHeight);
2110 mSelectedDateVerticalBar.draw(canvas);
2111 }
2112
2113 @Override
2114 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2115 mWidth = w;
2116 updateSelectionPositions();
2117 }
2118
2119 /**
2120 * This calculates the positions for the selected day lines.
2121 */
2122 private void updateSelectionPositions() {
2123 if (mHasSelectedDay) {
2124 final boolean isLayoutRtl = isLayoutRtl();
2125 int selectedPosition = mSelectedDay - mFirstDayOfWeek;
2126 if (selectedPosition < 0) {
2127 selectedPosition += 7;
2128 }
2129 if (mShowWeekNumber && !isLayoutRtl) {
2130 selectedPosition++;
2131 }
2132 if (isLayoutRtl) {
2133 mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
2134
2135 } else {
2136 mSelectedLeft = selectedPosition * mWidth / mNumCells;
2137 }
2138 mSelectedRight = mSelectedLeft + mWidth / mNumCells;
2139 }
2140 }
2141
2142 @Override
2143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2144 mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
2145 .getPaddingBottom()) / mShownWeekCount;
2146 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002147 }
2148 }
2149
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002150 }
Fabrice Di Meglioc6afe762013-08-29 15:24:19 -07002151
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002152}