CalendarView.java revision ef71947fd38f7918a628a238fee2ae000bb6bb45
1/*
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
19import android.annotation.Widget;
20import android.app.Service;
21import android.content.Context;
22import android.content.res.Configuration;
23import 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;
32import android.text.format.DateUtils;
33import 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;
42import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityNodeInfo;
44import android.widget.AbsListView.OnScrollListener;
45
46import com.android.internal.R;
47
48import java.text.ParseException;
49import java.text.SimpleDateFormat;
50import java.util.Calendar;
51import 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
65 * @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
74 */
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    private CalendarViewDelegate mDelegate;
84
85    /**
86     * 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) {
106        this(context, attrs, R.attr.calendarViewStyle);
107    }
108
109    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);
115
116        mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes);
117    }
118
119    /**
120     * Sets the number of weeks to be shown.
121     *
122     * @param count The shown week count.
123     *
124     * @attr ref android.R.styleable#CalendarView_shownWeekCount
125     */
126    public void setShownWeekCount(int count) {
127        mDelegate.setShownWeekCount(count);
128    }
129
130    /**
131     * Gets the number of weeks to be shown.
132     *
133     * @return The shown week count.
134     *
135     * @attr ref android.R.styleable#CalendarView_shownWeekCount
136     */
137    public int getShownWeekCount() {
138        return mDelegate.getShownWeekCount();
139    }
140
141    /**
142     * Sets the background color for the selected week.
143     *
144     * @param color The week background color.
145     *
146     * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
147     */
148    public void setSelectedWeekBackgroundColor(int color) {
149        mDelegate.setSelectedWeekBackgroundColor(color);
150    }
151
152    /**
153     * Gets the background color for the selected week.
154     *
155     * @return The week background color.
156     *
157     * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
158     */
159    public int getSelectedWeekBackgroundColor() {
160        return mDelegate.getSelectedWeekBackgroundColor();
161    }
162
163    /**
164     * Sets the color for the dates of the focused month.
165     *
166     * @param color The focused month date color.
167     *
168     * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
169     */
170    public void setFocusedMonthDateColor(int color) {
171        mDelegate.setFocusedMonthDateColor(color);
172    }
173
174    /**
175     * Gets the color for the dates in the focused month.
176     *
177     * @return The focused month date color.
178     *
179     * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
180     */
181    public int getFocusedMonthDateColor() {
182        return mDelegate.getFocusedMonthDateColor();
183    }
184
185    /**
186     * Sets the color for the dates of a not focused month.
187     *
188     * @param color A not focused month date color.
189     *
190     * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
191     */
192    public void setUnfocusedMonthDateColor(int color) {
193        mDelegate.setUnfocusedMonthDateColor(color);
194    }
195
196    /**
197     * Gets the color for the dates in a not focused month.
198     *
199     * @return A not focused month date color.
200     *
201     * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
202     */
203    public int getUnfocusedMonthDateColor() {
204        return mDelegate.getUnfocusedMonthDateColor();
205    }
206
207    /**
208     * Sets the color for the week numbers.
209     *
210     * @param color The week number color.
211     *
212     * @attr ref android.R.styleable#CalendarView_weekNumberColor
213     */
214    public void setWeekNumberColor(int color) {
215        mDelegate.setWeekNumberColor(color);
216    }
217
218    /**
219     * Gets the color for the week numbers.
220     *
221     * @return The week number color.
222     *
223     * @attr ref android.R.styleable#CalendarView_weekNumberColor
224     */
225    public int getWeekNumberColor() {
226        return mDelegate.getWeekNumberColor();
227    }
228
229    /**
230     * Sets the color for the separator line between weeks.
231     *
232     * @param color The week separator color.
233     *
234     * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
235     */
236    public void setWeekSeparatorLineColor(int color) {
237        mDelegate.setWeekSeparatorLineColor(color);
238    }
239
240    /**
241     * Gets the color for the separator line between weeks.
242     *
243     * @return The week separator color.
244     *
245     * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
246     */
247    public int getWeekSeparatorLineColor() {
248        return mDelegate.getWeekSeparatorLineColor();
249    }
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.
256     *
257     * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
258     */
259    public void setSelectedDateVerticalBar(int resourceId) {
260        mDelegate.setSelectedDateVerticalBar(resourceId);
261    }
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.
268     *
269     * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
270     */
271    public void setSelectedDateVerticalBar(Drawable drawable) {
272        mDelegate.setSelectedDateVerticalBar(drawable);
273    }
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() {
282        return mDelegate.getSelectedDateVerticalBar();
283    }
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.
289     *
290     * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
291     */
292    public void setWeekDayTextAppearance(int resourceId) {
293        mDelegate.setWeekDayTextAppearance(resourceId);
294    }
295
296    /**
297     * Gets the text appearance for the week day abbreviation of the calendar header.
298     *
299     * @return The text appearance resource id.
300     *
301     * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
302     */
303    public int getWeekDayTextAppearance() {
304        return mDelegate.getWeekDayTextAppearance();
305    }
306
307    /**
308     * Sets the text appearance for the calendar dates.
309     *
310     * @param resourceId The text appearance resource id.
311     *
312     * @attr ref android.R.styleable#CalendarView_dateTextAppearance
313     */
314    public void setDateTextAppearance(int resourceId) {
315        mDelegate.setDateTextAppearance(resourceId);
316    }
317
318    /**
319     * Gets the text appearance for the calendar dates.
320     *
321     * @return The text appearance resource id.
322     *
323     * @attr ref android.R.styleable#CalendarView_dateTextAppearance
324     */
325    public int getDateTextAppearance() {
326        return mDelegate.getDateTextAppearance();
327    }
328
329    @Override
330    public void setEnabled(boolean enabled) {
331        mDelegate.setEnabled(enabled);
332    }
333
334    @Override
335    public boolean isEnabled() {
336        return mDelegate.isEnabled();
337    }
338
339    /**
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.
348     *
349     * @attr ref android.R.styleable#CalendarView_minDate
350     */
351    public long getMinDate() {
352        return mDelegate.getMinDate();
353    }
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.
361     *
362     * @attr ref android.R.styleable#CalendarView_minDate
363     */
364    public void setMinDate(long minDate) {
365        mDelegate.setMinDate(minDate);
366    }
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.
377     *
378     * @attr ref android.R.styleable#CalendarView_maxDate
379     */
380    public long getMaxDate() {
381        return mDelegate.getMaxDate();
382    }
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.
390     *
391     * @attr ref android.R.styleable#CalendarView_maxDate
392     */
393    public void setMaxDate(long maxDate) {
394        mDelegate.setMaxDate(maxDate);
395    }
396
397    /**
398     * Sets whether to show the week number.
399     *
400     * @param showWeekNumber True to show the week number.
401     *
402     * @attr ref android.R.styleable#CalendarView_showWeekNumber
403     */
404    public void setShowWeekNumber(boolean showWeekNumber) {
405        mDelegate.setShowWeekNumber(showWeekNumber);
406    }
407
408    /**
409     * Gets whether to show the week number.
410     *
411     * @return True if showing the week number.
412     *
413     * @attr ref android.R.styleable#CalendarView_showWeekNumber
414     */
415    public boolean getShowWeekNumber() {
416        return mDelegate.getShowWeekNumber();
417    }
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
431     *
432     * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
433     */
434    public int getFirstDayOfWeek() {
435        return mDelegate.getFirstDayOfWeek();
436    }
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
450     *
451     * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
452     */
453    public void setFirstDayOfWeek(int firstDayOfWeek) {
454        mDelegate.setFirstDayOfWeek(firstDayOfWeek);
455    }
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) {
463        mDelegate.setOnDateChangeListener(listener);
464    }
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() {
473        return mDelegate.getDate();
474    }
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)
486     * @see #setMinDate(long)
487     * @see #setMaxDate(long)
488     */
489    public void setDate(long date) {
490        mDelegate.setDate(date);
491    }
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     *
504     * @see #setMinDate(long)
505     * @see #setMaxDate(long)
506     */
507    public void setDate(long date, boolean animate, boolean center) {
508        mDelegate.setDate(date, animate, center);
509    }
510
511    @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);
527    }
528
529    /**
530     * 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.
533     */
534    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;
615        }
616    }
617
618    /**
619     * A delegate implementing the legacy CalendarView
620     */
621    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 abbreviations for day of week names.
779         */
780        private String[] mDayNamesShort;
781
782        /**
783         * Cached full-length day of week names.
784         */
785        private String[] mDayNamesLong;
786
787        /**
788         * The first day of the week.
789         */
790        private int mFirstDayOfWeek;
791
792        /**
793         * Which month should be displayed/highlighted [0-11].
794         */
795        private int mCurrentMonthDisplayed = -1;
796
797        /**
798         * Used for tracking during a scroll.
799         */
800        private long mPreviousScrollPosition;
801
802        /**
803         * Used for tracking which direction the view is scrolling.
804         */
805        private boolean mIsScrollingUp = false;
806
807        /**
808         * The previous scroll state of the weeks ListView.
809         */
810        private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
811
812        /**
813         * The current scroll state of the weeks ListView.
814         */
815        private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
816
817        /**
818         * Listener for changes in the selected day.
819         */
820        private OnDateChangeListener mOnDateChangeListener;
821
822        /**
823         * Command for adjusting the position after a scroll/fling.
824         */
825        private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
826
827        /**
828         * Temporary instance to avoid multiple instantiations.
829         */
830        private Calendar mTempDate;
831
832        /**
833         * The first day of the focused month.
834         */
835        private Calendar mFirstDayOfMonth;
836
837        /**
838         * The start date of the range supported by this picker.
839         */
840        private Calendar mMinDate;
841
842        /**
843         * The end date of the range supported by this picker.
844         */
845        private Calendar mMaxDate;
846
847        /**
848         * Date format for parsing dates.
849         */
850        private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
851
852        LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs,
853                                   int defStyleAttr, int defStyleRes) {
854            super(delegator, context);
855
856            // initialization based on locale
857            setCurrentLocale(Locale.getDefault());
858
859            TypedArray attributesArray = context.obtainStyledAttributes(attrs,
860                    R.styleable.CalendarView, defStyleAttr, defStyleRes);
861            mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
862                    DEFAULT_SHOW_WEEK_NUMBER);
863            mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
864                    LocaleData.get(Locale.getDefault()).firstDayOfWeek);
865            String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
866            if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
867                parseDate(DEFAULT_MIN_DATE, mMinDate);
868            }
869            String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
870            if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
871                parseDate(DEFAULT_MAX_DATE, mMaxDate);
872            }
873            if (mMaxDate.before(mMinDate)) {
874                throw new IllegalArgumentException("Max date cannot be before min date.");
875            }
876            mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
877                    DEFAULT_SHOWN_WEEK_COUNT);
878            mSelectedWeekBackgroundColor = attributesArray.getColor(
879                    R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
880            mFocusedMonthDateColor = attributesArray.getColor(
881                    R.styleable.CalendarView_focusedMonthDateColor, 0);
882            mUnfocusedMonthDateColor = attributesArray.getColor(
883                    R.styleable.CalendarView_unfocusedMonthDateColor, 0);
884            mWeekSeparatorLineColor = attributesArray.getColor(
885                    R.styleable.CalendarView_weekSeparatorLineColor, 0);
886            mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
887            mSelectedDateVerticalBar = attributesArray.getDrawable(
888                    R.styleable.CalendarView_selectedDateVerticalBar);
889
890            mDateTextAppearanceResId = attributesArray.getResourceId(
891                    R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
892            updateDateTextSize();
893
894            mWeekDayTextAppearanceResId = attributesArray.getResourceId(
895                    R.styleable.CalendarView_weekDayTextAppearance,
896                    DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
897            attributesArray.recycle();
898
899            DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
900            mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
901                    UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
902            mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
903                    UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
904            mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
905                    UNSCALED_BOTTOM_BUFFER, displayMetrics);
906            mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
907                    UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
908            mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
909                    UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
910
911            LayoutInflater layoutInflater = (LayoutInflater) mContext
912                    .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
913            View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
914            mDelegator.addView(content);
915
916            mListView = (ListView) mDelegator.findViewById(R.id.list);
917            mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
918            mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
919
920            setUpHeader();
921            setUpListView();
922            setUpAdapter();
923
924            // go to today or whichever is close to today min or max date
925            mTempDate.setTimeInMillis(System.currentTimeMillis());
926            if (mTempDate.before(mMinDate)) {
927                goTo(mMinDate, false, true, true);
928            } else if (mMaxDate.before(mTempDate)) {
929                goTo(mMaxDate, false, true, true);
930            } else {
931                goTo(mTempDate, false, true, true);
932            }
933
934            mDelegator.invalidate();
935        }
936
937        @Override
938        public void setShownWeekCount(int count) {
939            if (mShownWeekCount != count) {
940                mShownWeekCount = count;
941                mDelegator.invalidate();
942            }
943        }
944
945        @Override
946        public int getShownWeekCount() {
947            return mShownWeekCount;
948        }
949
950        @Override
951        public void setSelectedWeekBackgroundColor(int color) {
952            if (mSelectedWeekBackgroundColor != color) {
953                mSelectedWeekBackgroundColor = color;
954                final int childCount = mListView.getChildCount();
955                for (int i = 0; i < childCount; i++) {
956                    WeekView weekView = (WeekView) mListView.getChildAt(i);
957                    if (weekView.mHasSelectedDay) {
958                        weekView.invalidate();
959                    }
960                }
961            }
962        }
963
964        @Override
965        public int getSelectedWeekBackgroundColor() {
966            return mSelectedWeekBackgroundColor;
967        }
968
969        @Override
970        public void setFocusedMonthDateColor(int color) {
971            if (mFocusedMonthDateColor != color) {
972                mFocusedMonthDateColor = color;
973                final int childCount = mListView.getChildCount();
974                for (int i = 0; i < childCount; i++) {
975                    WeekView weekView = (WeekView) mListView.getChildAt(i);
976                    if (weekView.mHasFocusedDay) {
977                        weekView.invalidate();
978                    }
979                }
980            }
981        }
982
983        @Override
984        public int getFocusedMonthDateColor() {
985            return mFocusedMonthDateColor;
986        }
987
988        @Override
989        public void setUnfocusedMonthDateColor(int color) {
990            if (mUnfocusedMonthDateColor != color) {
991                mUnfocusedMonthDateColor = color;
992                final int childCount = mListView.getChildCount();
993                for (int i = 0; i < childCount; i++) {
994                    WeekView weekView = (WeekView) mListView.getChildAt(i);
995                    if (weekView.mHasUnfocusedDay) {
996                        weekView.invalidate();
997                    }
998                }
999            }
1000        }
1001
1002        @Override
1003        public int getUnfocusedMonthDateColor() {
1004            return mFocusedMonthDateColor;
1005        }
1006
1007        @Override
1008        public void setWeekNumberColor(int color) {
1009            if (mWeekNumberColor != color) {
1010                mWeekNumberColor = color;
1011                if (mShowWeekNumber) {
1012                    invalidateAllWeekViews();
1013                }
1014            }
1015        }
1016
1017        @Override
1018        public int getWeekNumberColor() {
1019            return mWeekNumberColor;
1020        }
1021
1022        @Override
1023        public void setWeekSeparatorLineColor(int color) {
1024            if (mWeekSeparatorLineColor != color) {
1025                mWeekSeparatorLineColor = color;
1026                invalidateAllWeekViews();
1027            }
1028        }
1029
1030        @Override
1031        public int getWeekSeparatorLineColor() {
1032            return mWeekSeparatorLineColor;
1033        }
1034
1035        @Override
1036        public void setSelectedDateVerticalBar(int resourceId) {
1037            Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
1038            setSelectedDateVerticalBar(drawable);
1039        }
1040
1041        @Override
1042        public void setSelectedDateVerticalBar(Drawable drawable) {
1043            if (mSelectedDateVerticalBar != drawable) {
1044                mSelectedDateVerticalBar = drawable;
1045                final int childCount = mListView.getChildCount();
1046                for (int i = 0; i < childCount; i++) {
1047                    WeekView weekView = (WeekView) mListView.getChildAt(i);
1048                    if (weekView.mHasSelectedDay) {
1049                        weekView.invalidate();
1050                    }
1051                }
1052            }
1053        }
1054
1055        @Override
1056        public Drawable getSelectedDateVerticalBar() {
1057            return mSelectedDateVerticalBar;
1058        }
1059
1060        @Override
1061        public void setWeekDayTextAppearance(int resourceId) {
1062            if (mWeekDayTextAppearanceResId != resourceId) {
1063                mWeekDayTextAppearanceResId = resourceId;
1064                setUpHeader();
1065            }
1066        }
1067
1068        @Override
1069        public int getWeekDayTextAppearance() {
1070            return mWeekDayTextAppearanceResId;
1071        }
1072
1073        @Override
1074        public void setDateTextAppearance(int resourceId) {
1075            if (mDateTextAppearanceResId != resourceId) {
1076                mDateTextAppearanceResId = resourceId;
1077                updateDateTextSize();
1078                invalidateAllWeekViews();
1079            }
1080        }
1081
1082        @Override
1083        public int getDateTextAppearance() {
1084            return mDateTextAppearanceResId;
1085        }
1086
1087        @Override
1088        public void setEnabled(boolean enabled) {
1089            mListView.setEnabled(enabled);
1090        }
1091
1092        @Override
1093        public boolean isEnabled() {
1094            return mListView.isEnabled();
1095        }
1096
1097        @Override
1098        public void setMinDate(long minDate) {
1099            mTempDate.setTimeInMillis(minDate);
1100            if (isSameDate(mTempDate, mMinDate)) {
1101                return;
1102            }
1103            mMinDate.setTimeInMillis(minDate);
1104            // make sure the current date is not earlier than
1105            // the new min date since the latter is used for
1106            // calculating the indices in the adapter thus
1107            // avoiding out of bounds error
1108            Calendar date = mAdapter.mSelectedDate;
1109            if (date.before(mMinDate)) {
1110                mAdapter.setSelectedDay(mMinDate);
1111            }
1112            // reinitialize the adapter since its range depends on min date
1113            mAdapter.init();
1114            if (date.before(mMinDate)) {
1115                setDate(mTempDate.getTimeInMillis());
1116            } else {
1117                // we go to the current date to force the ListView to query its
1118                // adapter for the shown views since we have changed the adapter
1119                // range and the base from which the later calculates item indices
1120                // note that calling setDate will not work since the date is the same
1121                goTo(date, false, true, false);
1122            }
1123        }
1124
1125        @Override
1126        public long getMinDate() {
1127            return mMinDate.getTimeInMillis();
1128        }
1129
1130        @Override
1131        public void setMaxDate(long maxDate) {
1132            mTempDate.setTimeInMillis(maxDate);
1133            if (isSameDate(mTempDate, mMaxDate)) {
1134                return;
1135            }
1136            mMaxDate.setTimeInMillis(maxDate);
1137            // reinitialize the adapter since its range depends on max date
1138            mAdapter.init();
1139            Calendar date = mAdapter.mSelectedDate;
1140            if (date.after(mMaxDate)) {
1141                setDate(mMaxDate.getTimeInMillis());
1142            } else {
1143                // we go to the current date to force the ListView to query its
1144                // adapter for the shown views since we have changed the adapter
1145                // range and the base from which the later calculates item indices
1146                // note that calling setDate will not work since the date is the same
1147                goTo(date, false, true, false);
1148            }
1149        }
1150
1151        @Override
1152        public long getMaxDate() {
1153            return mMaxDate.getTimeInMillis();
1154        }
1155
1156        @Override
1157        public void setShowWeekNumber(boolean showWeekNumber) {
1158            if (mShowWeekNumber == showWeekNumber) {
1159                return;
1160            }
1161            mShowWeekNumber = showWeekNumber;
1162            mAdapter.notifyDataSetChanged();
1163            setUpHeader();
1164        }
1165
1166        @Override
1167        public boolean getShowWeekNumber() {
1168            return mShowWeekNumber;
1169        }
1170
1171        @Override
1172        public void setFirstDayOfWeek(int firstDayOfWeek) {
1173            if (mFirstDayOfWeek == firstDayOfWeek) {
1174                return;
1175            }
1176            mFirstDayOfWeek = firstDayOfWeek;
1177            mAdapter.init();
1178            mAdapter.notifyDataSetChanged();
1179            setUpHeader();
1180        }
1181
1182        @Override
1183        public int getFirstDayOfWeek() {
1184            return mFirstDayOfWeek;
1185        }
1186
1187        @Override
1188        public void setDate(long date) {
1189            setDate(date, false, false);
1190        }
1191
1192        @Override
1193        public void setDate(long date, boolean animate, boolean center) {
1194            mTempDate.setTimeInMillis(date);
1195            if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
1196                return;
1197            }
1198            goTo(mTempDate, animate, true, center);
1199        }
1200
1201        @Override
1202        public long getDate() {
1203            return mAdapter.mSelectedDate.getTimeInMillis();
1204        }
1205
1206        @Override
1207        public void setOnDateChangeListener(OnDateChangeListener listener) {
1208            mOnDateChangeListener = listener;
1209        }
1210
1211        @Override
1212        public void onConfigurationChanged(Configuration newConfig) {
1213            setCurrentLocale(newConfig.locale);
1214        }
1215
1216        @Override
1217        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1218            event.setClassName(CalendarView.class.getName());
1219        }
1220
1221        @Override
1222        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1223            info.setClassName(CalendarView.class.getName());
1224        }
1225
1226        /**
1227         * Sets the current locale.
1228         *
1229         * @param locale The current locale.
1230         */
1231        @Override
1232        protected void setCurrentLocale(Locale locale) {
1233            super.setCurrentLocale(locale);
1234
1235            mTempDate = getCalendarForLocale(mTempDate, locale);
1236            mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
1237            mMinDate = getCalendarForLocale(mMinDate, locale);
1238            mMaxDate = getCalendarForLocale(mMaxDate, locale);
1239        }
1240        private void updateDateTextSize() {
1241            TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
1242                    mDateTextAppearanceResId, R.styleable.TextAppearance);
1243            mDateTextSize = dateTextAppearance.getDimensionPixelSize(
1244                    R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
1245            dateTextAppearance.recycle();
1246        }
1247
1248        /**
1249         * Invalidates all week views.
1250         */
1251        private void invalidateAllWeekViews() {
1252            final int childCount = mListView.getChildCount();
1253            for (int i = 0; i < childCount; i++) {
1254                View view = mListView.getChildAt(i);
1255                view.invalidate();
1256            }
1257        }
1258
1259        /**
1260         * Gets a calendar for locale bootstrapped with the value of a given calendar.
1261         *
1262         * @param oldCalendar The old calendar.
1263         * @param locale The locale.
1264         */
1265        private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
1266            if (oldCalendar == null) {
1267                return Calendar.getInstance(locale);
1268            } else {
1269                final long currentTimeMillis = oldCalendar.getTimeInMillis();
1270                Calendar newCalendar = Calendar.getInstance(locale);
1271                newCalendar.setTimeInMillis(currentTimeMillis);
1272                return newCalendar;
1273            }
1274        }
1275
1276        /**
1277         * @return True if the <code>firstDate</code> is the same as the <code>
1278         * secondDate</code>.
1279         */
1280        private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
1281            return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
1282                    && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
1283        }
1284
1285        /**
1286         * Creates a new adapter if necessary and sets up its parameters.
1287         */
1288        private void setUpAdapter() {
1289            if (mAdapter == null) {
1290                mAdapter = new WeeksAdapter(mContext);
1291                mAdapter.registerDataSetObserver(new DataSetObserver() {
1292                    @Override
1293                    public void onChanged() {
1294                        if (mOnDateChangeListener != null) {
1295                            Calendar selectedDay = mAdapter.getSelectedDay();
1296                            mOnDateChangeListener.onSelectedDayChange(mDelegator,
1297                                    selectedDay.get(Calendar.YEAR),
1298                                    selectedDay.get(Calendar.MONTH),
1299                                    selectedDay.get(Calendar.DAY_OF_MONTH));
1300                        }
1301                    }
1302                });
1303                mListView.setAdapter(mAdapter);
1304            }
1305
1306            // refresh the view with the new parameters
1307            mAdapter.notifyDataSetChanged();
1308        }
1309
1310        /**
1311         * Sets up the strings to be used by the header.
1312         */
1313        private void setUpHeader() {
1314            mDayNamesShort = new String[mDaysPerWeek];
1315            mDayNamesLong = new String[mDaysPerWeek];
1316            for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
1317                int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
1318                mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
1319                        DateUtils.LENGTH_SHORTEST);
1320                mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
1321                        DateUtils.LENGTH_LONG);
1322            }
1323
1324            TextView label = (TextView) mDayNamesHeader.getChildAt(0);
1325            if (mShowWeekNumber) {
1326                label.setVisibility(View.VISIBLE);
1327            } else {
1328                label.setVisibility(View.GONE);
1329            }
1330            for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
1331                label = (TextView) mDayNamesHeader.getChildAt(i);
1332                if (mWeekDayTextAppearanceResId > -1) {
1333                    label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
1334                }
1335                if (i < mDaysPerWeek + 1) {
1336                    label.setText(mDayNamesShort[i - 1]);
1337                    label.setContentDescription(mDayNamesLong[i - 1]);
1338                    label.setVisibility(View.VISIBLE);
1339                } else {
1340                    label.setVisibility(View.GONE);
1341                }
1342            }
1343            mDayNamesHeader.invalidate();
1344        }
1345
1346        /**
1347         * Sets all the required fields for the list view.
1348         */
1349        private void setUpListView() {
1350            // Configure the listview
1351            mListView.setDivider(null);
1352            mListView.setItemsCanFocus(true);
1353            mListView.setVerticalScrollBarEnabled(false);
1354            mListView.setOnScrollListener(new OnScrollListener() {
1355                public void onScrollStateChanged(AbsListView view, int scrollState) {
1356                    LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState);
1357                }
1358
1359                public void onScroll(
1360                        AbsListView view, int firstVisibleItem, int visibleItemCount,
1361                        int totalItemCount) {
1362                    LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem,
1363                            visibleItemCount, totalItemCount);
1364                }
1365            });
1366            // Make the scrolling behavior nicer
1367            mListView.setFriction(mFriction);
1368            mListView.setVelocityScale(mVelocityScale);
1369        }
1370
1371        /**
1372         * This moves to the specified time in the view. If the time is not already
1373         * in range it will move the list so that the first of the month containing
1374         * the time is at the top of the view. If the new time is already in view
1375         * the list will not be scrolled unless forceScroll is true. This time may
1376         * optionally be highlighted as selected as well.
1377         *
1378         * @param date The time to move to.
1379         * @param animate Whether to scroll to the given time or just redraw at the
1380         *            new location.
1381         * @param setSelected Whether to set the given time as selected.
1382         * @param forceScroll Whether to recenter even if the time is already
1383         *            visible.
1384         *
1385         * @throws IllegalArgumentException of the provided date is before the
1386         *        range start of after the range end.
1387         */
1388        private void goTo(Calendar date, boolean animate, boolean setSelected,
1389                boolean forceScroll) {
1390            if (date.before(mMinDate) || date.after(mMaxDate)) {
1391                throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
1392                        + " and " + mMaxDate.getTime());
1393            }
1394            // Find the first and last entirely visible weeks
1395            int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
1396            View firstChild = mListView.getChildAt(0);
1397            if (firstChild != null && firstChild.getTop() < 0) {
1398                firstFullyVisiblePosition++;
1399            }
1400            int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
1401            if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
1402                lastFullyVisiblePosition--;
1403            }
1404            if (setSelected) {
1405                mAdapter.setSelectedDay(date);
1406            }
1407            // Get the week we're going to
1408            int position = getWeeksSinceMinDate(date);
1409
1410            // Check if the selected day is now outside of our visible range
1411            // and if so scroll to the month that contains it
1412            if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
1413                    || forceScroll) {
1414                mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
1415                mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
1416
1417                setMonthDisplayed(mFirstDayOfMonth);
1418
1419                // the earliest time we can scroll to is the min date
1420                if (mFirstDayOfMonth.before(mMinDate)) {
1421                    position = 0;
1422                } else {
1423                    position = getWeeksSinceMinDate(mFirstDayOfMonth);
1424                }
1425
1426                mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
1427                if (animate) {
1428                    mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
1429                            GOTO_SCROLL_DURATION);
1430                } else {
1431                    mListView.setSelectionFromTop(position, mListScrollTopOffset);
1432                    // Perform any after scroll operations that are needed
1433                    onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
1434                }
1435            } else if (setSelected) {
1436                // Otherwise just set the selection
1437                setMonthDisplayed(date);
1438            }
1439        }
1440
1441        /**
1442         * Parses the given <code>date</code> and in case of success sets
1443         * the result to the <code>outDate</code>.
1444         *
1445         * @return True if the date was parsed.
1446         */
1447        private boolean parseDate(String date, Calendar outDate) {
1448            try {
1449                outDate.setTime(mDateFormat.parse(date));
1450                return true;
1451            } catch (ParseException e) {
1452                Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
1453                return false;
1454            }
1455        }
1456
1457        /**
1458         * Called when a <code>view</code> transitions to a new <code>scrollState
1459         * </code>.
1460         */
1461        private void onScrollStateChanged(AbsListView view, int scrollState) {
1462            mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
1463        }
1464
1465        /**
1466         * Updates the title and selected month if the <code>view</code> has moved to a new
1467         * month.
1468         */
1469        private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1470                              int totalItemCount) {
1471            WeekView child = (WeekView) view.getChildAt(0);
1472            if (child == null) {
1473                return;
1474            }
1475
1476            // Figure out where we are
1477            long currScroll =
1478                    view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
1479
1480            // If we have moved since our last call update the direction
1481            if (currScroll < mPreviousScrollPosition) {
1482                mIsScrollingUp = true;
1483            } else if (currScroll > mPreviousScrollPosition) {
1484                mIsScrollingUp = false;
1485            } else {
1486                return;
1487            }
1488
1489            // Use some hysteresis for checking which month to highlight. This
1490            // causes the month to transition when two full weeks of a month are
1491            // visible when scrolling up, and when the first day in a month reaches
1492            // the top of the screen when scrolling down.
1493            int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
1494            if (mIsScrollingUp) {
1495                child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
1496            } else if (offset != 0) {
1497                child = (WeekView) view.getChildAt(offset);
1498            }
1499
1500            if (child != null) {
1501                // Find out which month we're moving into
1502                int month;
1503                if (mIsScrollingUp) {
1504                    month = child.getMonthOfFirstWeekDay();
1505                } else {
1506                    month = child.getMonthOfLastWeekDay();
1507                }
1508
1509                // And how it relates to our current highlighted month
1510                int monthDiff;
1511                if (mCurrentMonthDisplayed == 11 && month == 0) {
1512                    monthDiff = 1;
1513                } else if (mCurrentMonthDisplayed == 0 && month == 11) {
1514                    monthDiff = -1;
1515                } else {
1516                    monthDiff = month - mCurrentMonthDisplayed;
1517                }
1518
1519                // Only switch months if we're scrolling away from the currently
1520                // selected month
1521                if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
1522                    Calendar firstDay = child.getFirstDay();
1523                    if (mIsScrollingUp) {
1524                        firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
1525                    } else {
1526                        firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
1527                    }
1528                    setMonthDisplayed(firstDay);
1529                }
1530            }
1531            mPreviousScrollPosition = currScroll;
1532            mPreviousScrollState = mCurrentScrollState;
1533        }
1534
1535        /**
1536         * Sets the month displayed at the top of this view based on time. Override
1537         * to add custom events when the title is changed.
1538         *
1539         * @param calendar A day in the new focus month.
1540         */
1541        private void setMonthDisplayed(Calendar calendar) {
1542            mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
1543            mAdapter.setFocusMonth(mCurrentMonthDisplayed);
1544            final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
1545                    | DateUtils.FORMAT_SHOW_YEAR;
1546            final long millis = calendar.getTimeInMillis();
1547            String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
1548            mMonthName.setText(newMonthName);
1549            mMonthName.invalidate();
1550        }
1551
1552        /**
1553         * @return Returns the number of weeks between the current <code>date</code>
1554         *         and the <code>mMinDate</code>.
1555         */
1556        private int getWeeksSinceMinDate(Calendar date) {
1557            if (date.before(mMinDate)) {
1558                throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
1559                        + " does not precede toDate: " + date.getTime());
1560            }
1561            long endTimeMillis = date.getTimeInMillis()
1562                    + date.getTimeZone().getOffset(date.getTimeInMillis());
1563            long startTimeMillis = mMinDate.getTimeInMillis()
1564                    + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
1565            long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
1566                    * MILLIS_IN_DAY;
1567            return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
1568        }
1569
1570        /**
1571         * Command responsible for acting upon scroll state changes.
1572         */
1573        private class ScrollStateRunnable implements Runnable {
1574            private AbsListView mView;
1575
1576            private int mNewState;
1577
1578            /**
1579             * Sets up the runnable with a short delay in case the scroll state
1580             * immediately changes again.
1581             *
1582             * @param view The list view that changed state
1583             * @param scrollState The new state it changed to
1584             */
1585            public void doScrollStateChange(AbsListView view, int scrollState) {
1586                mView = view;
1587                mNewState = scrollState;
1588                mDelegator.removeCallbacks(this);
1589                mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
1590            }
1591
1592            public void run() {
1593                mCurrentScrollState = mNewState;
1594                // Fix the position after a scroll or a fling ends
1595                if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
1596                        && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
1597                    View child = mView.getChildAt(0);
1598                    if (child == null) {
1599                        // The view is no longer visible, just return
1600                        return;
1601                    }
1602                    int dist = child.getBottom() - mListScrollTopOffset;
1603                    if (dist > mListScrollTopOffset) {
1604                        if (mIsScrollingUp) {
1605                            mView.smoothScrollBy(dist - child.getHeight(),
1606                                    ADJUSTMENT_SCROLL_DURATION);
1607                        } else {
1608                            mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
1609                        }
1610                    }
1611                }
1612                mPreviousScrollState = mNewState;
1613            }
1614        }
1615
1616        /**
1617         * <p>
1618         * This is a specialized adapter for creating a list of weeks with
1619         * selectable days. It can be configured to display the week number, start
1620         * the week on a given day, show a reduced number of days, or display an
1621         * arbitrary number of weeks at a time.
1622         * </p>
1623         */
1624        private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
1625
1626            private int mSelectedWeek;
1627
1628            private GestureDetector mGestureDetector;
1629
1630            private int mFocusedMonth;
1631
1632            private final Calendar mSelectedDate = Calendar.getInstance();
1633
1634            private int mTotalWeekCount;
1635
1636            public WeeksAdapter(Context context) {
1637                mContext = context;
1638                mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
1639                init();
1640            }
1641
1642            /**
1643             * Set up the gesture detector and selected time
1644             */
1645            private void init() {
1646                mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1647                mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
1648                if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
1649                        || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
1650                    mTotalWeekCount++;
1651                }
1652                notifyDataSetChanged();
1653            }
1654
1655            /**
1656             * Updates the selected day and related parameters.
1657             *
1658             * @param selectedDay The time to highlight
1659             */
1660            public void setSelectedDay(Calendar selectedDay) {
1661                if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
1662                        && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
1663                    return;
1664                }
1665                mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
1666                mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1667                mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
1668                notifyDataSetChanged();
1669            }
1670
1671            /**
1672             * @return The selected day of month.
1673             */
1674            public Calendar getSelectedDay() {
1675                return mSelectedDate;
1676            }
1677
1678            @Override
1679            public int getCount() {
1680                return mTotalWeekCount;
1681            }
1682
1683            @Override
1684            public Object getItem(int position) {
1685                return null;
1686            }
1687
1688            @Override
1689            public long getItemId(int position) {
1690                return position;
1691            }
1692
1693            @Override
1694            public View getView(int position, View convertView, ViewGroup parent) {
1695                WeekView weekView = null;
1696                if (convertView != null) {
1697                    weekView = (WeekView) convertView;
1698                } else {
1699                    weekView = new WeekView(mContext);
1700                    android.widget.AbsListView.LayoutParams params =
1701                            new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
1702                                    LayoutParams.WRAP_CONTENT);
1703                    weekView.setLayoutParams(params);
1704                    weekView.setClickable(true);
1705                    weekView.setOnTouchListener(this);
1706                }
1707
1708                int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1709                        Calendar.DAY_OF_WEEK) : -1;
1710                weekView.init(position, selectedWeekDay, mFocusedMonth);
1711
1712                return weekView;
1713            }
1714
1715            /**
1716             * Changes which month is in focus and updates the view.
1717             *
1718             * @param month The month to show as in focus [0-11]
1719             */
1720            public void setFocusMonth(int month) {
1721                if (mFocusedMonth == month) {
1722                    return;
1723                }
1724                mFocusedMonth = month;
1725                notifyDataSetChanged();
1726            }
1727
1728            @Override
1729            public boolean onTouch(View v, MotionEvent event) {
1730                if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
1731                    WeekView weekView = (WeekView) v;
1732                    // if we cannot find a day for the given location we are done
1733                    if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
1734                        return true;
1735                    }
1736                    // it is possible that the touched day is outside the valid range
1737                    // we draw whole weeks but range end can fall not on the week end
1738                    if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1739                        return true;
1740                    }
1741                    onDateTapped(mTempDate);
1742                    return true;
1743                }
1744                return false;
1745            }
1746
1747            /**
1748             * Maintains the same hour/min/sec but moves the day to the tapped day.
1749             *
1750             * @param day The day that was tapped
1751             */
1752            private void onDateTapped(Calendar day) {
1753                setSelectedDay(day);
1754                setMonthDisplayed(day);
1755            }
1756
1757            /**
1758             * This is here so we can identify single tap events and set the
1759             * selected day correctly
1760             */
1761            class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
1762                @Override
1763                public boolean onSingleTapUp(MotionEvent e) {
1764                    return true;
1765                }
1766            }
1767        }
1768
1769        /**
1770         * <p>
1771         * This is a dynamic view for drawing a single week. It can be configured to
1772         * display the week number, start the week on a given day, or show a reduced
1773         * number of days. It is intended for use as a single view within a
1774         * ListView. See {@link WeeksAdapter} for usage.
1775         * </p>
1776         */
1777        private class WeekView extends View {
1778
1779            private final Rect mTempRect = new Rect();
1780
1781            private final Paint mDrawPaint = new Paint();
1782
1783            private final Paint mMonthNumDrawPaint = new Paint();
1784
1785            // Cache the number strings so we don't have to recompute them each time
1786            private String[] mDayNumbers;
1787
1788            // Quick lookup for checking which days are in the focus month
1789            private boolean[] mFocusDay;
1790
1791            // Whether this view has a focused day.
1792            private boolean mHasFocusedDay;
1793
1794            // Whether this view has only focused days.
1795            private boolean mHasUnfocusedDay;
1796
1797            // The first day displayed by this item
1798            private Calendar mFirstDay;
1799
1800            // The month of the first day in this week
1801            private int mMonthOfFirstWeekDay = -1;
1802
1803            // The month of the last day in this week
1804            private int mLastWeekDayMonth = -1;
1805
1806            // The position of this week, equivalent to weeks since the week of Jan
1807            // 1st, 1900
1808            private int mWeek = -1;
1809
1810            // Quick reference to the width of this view, matches parent
1811            private int mWidth;
1812
1813            // The height this view should draw at in pixels, set by height param
1814            private int mHeight;
1815
1816            // If this view contains the selected day
1817            private boolean mHasSelectedDay = false;
1818
1819            // Which day is selected [0-6] or -1 if no day is selected
1820            private int mSelectedDay = -1;
1821
1822            // The number of days + a spot for week number if it is displayed
1823            private int mNumCells;
1824
1825            // The left edge of the selected day
1826            private int mSelectedLeft = -1;
1827
1828            // The right edge of the selected day
1829            private int mSelectedRight = -1;
1830
1831            public WeekView(Context context) {
1832                super(context);
1833
1834                // Sets up any standard paints that will be used
1835                initilaizePaints();
1836            }
1837
1838            /**
1839             * Initializes this week view.
1840             *
1841             * @param weekNumber The number of the week this view represents. The
1842             *            week number is a zero based index of the weeks since
1843             *            {@link CalendarView#getMinDate()}.
1844             * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
1845             *            selected day.
1846             * @param focusedMonth The month that is currently in focus i.e.
1847             *            highlighted.
1848             */
1849            public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
1850                mSelectedDay = selectedWeekDay;
1851                mHasSelectedDay = mSelectedDay != -1;
1852                mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
1853                mWeek = weekNumber;
1854                mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
1855
1856                mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1857                mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
1858
1859                // Allocate space for caching the day numbers and focus values
1860                mDayNumbers = new String[mNumCells];
1861                mFocusDay = new boolean[mNumCells];
1862
1863                // If we're showing the week number calculate it based on Monday
1864                int i = 0;
1865                if (mShowWeekNumber) {
1866                    mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1867                            mTempDate.get(Calendar.WEEK_OF_YEAR));
1868                    i++;
1869                }
1870
1871                // Now adjust our starting day based on the start day of the week
1872                int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
1873                mTempDate.add(Calendar.DAY_OF_MONTH, diff);
1874
1875                mFirstDay = (Calendar) mTempDate.clone();
1876                mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
1877
1878                mHasUnfocusedDay = true;
1879                for (; i < mNumCells; i++) {
1880                    final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
1881                    mFocusDay[i] = isFocusedDay;
1882                    mHasFocusedDay |= isFocusedDay;
1883                    mHasUnfocusedDay &= !isFocusedDay;
1884                    // do not draw dates outside the valid range to avoid user confusion
1885                    if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1886                        mDayNumbers[i] = "";
1887                    } else {
1888                        mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
1889                                mTempDate.get(Calendar.DAY_OF_MONTH));
1890                    }
1891                    mTempDate.add(Calendar.DAY_OF_MONTH, 1);
1892                }
1893                // We do one extra add at the end of the loop, if that pushed us to
1894                // new month undo it
1895                if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
1896                    mTempDate.add(Calendar.DAY_OF_MONTH, -1);
1897                }
1898                mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
1899
1900                updateSelectionPositions();
1901            }
1902
1903            /**
1904             * Initialize the paint instances.
1905             */
1906            private void initilaizePaints() {
1907                mDrawPaint.setFakeBoldText(false);
1908                mDrawPaint.setAntiAlias(true);
1909                mDrawPaint.setStyle(Style.FILL);
1910
1911                mMonthNumDrawPaint.setFakeBoldText(true);
1912                mMonthNumDrawPaint.setAntiAlias(true);
1913                mMonthNumDrawPaint.setStyle(Style.FILL);
1914                mMonthNumDrawPaint.setTextAlign(Align.CENTER);
1915                mMonthNumDrawPaint.setTextSize(mDateTextSize);
1916            }
1917
1918            /**
1919             * Returns the month of the first day in this week.
1920             *
1921             * @return The month the first day of this view is in.
1922             */
1923            public int getMonthOfFirstWeekDay() {
1924                return mMonthOfFirstWeekDay;
1925            }
1926
1927            /**
1928             * Returns the month of the last day in this week
1929             *
1930             * @return The month the last day of this view is in
1931             */
1932            public int getMonthOfLastWeekDay() {
1933                return mLastWeekDayMonth;
1934            }
1935
1936            /**
1937             * Returns the first day in this view.
1938             *
1939             * @return The first day in the view.
1940             */
1941            public Calendar getFirstDay() {
1942                return mFirstDay;
1943            }
1944
1945            /**
1946             * Calculates the day that the given x position is in, accounting for
1947             * week number.
1948             *
1949             * @param x The x position of the touch event.
1950             * @return True if a day was found for the given location.
1951             */
1952            public boolean getDayFromLocation(float x, Calendar outCalendar) {
1953                final boolean isLayoutRtl = isLayoutRtl();
1954
1955                int start;
1956                int end;
1957
1958                if (isLayoutRtl) {
1959                    start = 0;
1960                    end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1961                } else {
1962                    start = mShowWeekNumber ? mWidth / mNumCells : 0;
1963                    end = mWidth;
1964                }
1965
1966                if (x < start || x > end) {
1967                    outCalendar.clear();
1968                    return false;
1969                }
1970
1971                // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
1972                int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
1973
1974                if (isLayoutRtl) {
1975                    dayPosition = mDaysPerWeek - 1 - dayPosition;
1976                }
1977
1978                outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
1979                outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
1980
1981                return true;
1982            }
1983
1984            @Override
1985            protected void onDraw(Canvas canvas) {
1986                drawBackground(canvas);
1987                drawWeekNumbersAndDates(canvas);
1988                drawWeekSeparators(canvas);
1989                drawSelectedDateVerticalBars(canvas);
1990            }
1991
1992            /**
1993             * This draws the selection highlight if a day is selected in this week.
1994             *
1995             * @param canvas The canvas to draw on
1996             */
1997            private void drawBackground(Canvas canvas) {
1998                if (!mHasSelectedDay) {
1999                    return;
2000                }
2001                mDrawPaint.setColor(mSelectedWeekBackgroundColor);
2002
2003                mTempRect.top = mWeekSeperatorLineWidth;
2004                mTempRect.bottom = mHeight;
2005
2006                final boolean isLayoutRtl = isLayoutRtl();
2007
2008                if (isLayoutRtl) {
2009                    mTempRect.left = 0;
2010                    mTempRect.right = mSelectedLeft - 2;
2011                } else {
2012                    mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
2013                    mTempRect.right = mSelectedLeft - 2;
2014                }
2015                canvas.drawRect(mTempRect, mDrawPaint);
2016
2017                if (isLayoutRtl) {
2018                    mTempRect.left = mSelectedRight + 3;
2019                    mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2020                } else {
2021                    mTempRect.left = mSelectedRight + 3;
2022                    mTempRect.right = mWidth;
2023                }
2024                canvas.drawRect(mTempRect, mDrawPaint);
2025            }
2026
2027            /**
2028             * Draws the week and month day numbers for this week.
2029             *
2030             * @param canvas The canvas to draw on
2031             */
2032            private void drawWeekNumbersAndDates(Canvas canvas) {
2033                final float textHeight = mDrawPaint.getTextSize();
2034                final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
2035                final int nDays = mNumCells;
2036                final int divisor = 2 * nDays;
2037
2038                mDrawPaint.setTextAlign(Align.CENTER);
2039                mDrawPaint.setTextSize(mDateTextSize);
2040
2041                int i = 0;
2042
2043                if (isLayoutRtl()) {
2044                    for (; i < nDays - 1; i++) {
2045                        mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2046                                : mUnfocusedMonthDateColor);
2047                        int x = (2 * i + 1) * mWidth / divisor;
2048                        canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
2049                    }
2050                    if (mShowWeekNumber) {
2051                        mDrawPaint.setColor(mWeekNumberColor);
2052                        int x = mWidth - mWidth / divisor;
2053                        canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2054                    }
2055                } else {
2056                    if (mShowWeekNumber) {
2057                        mDrawPaint.setColor(mWeekNumberColor);
2058                        int x = mWidth / divisor;
2059                        canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2060                        i++;
2061                    }
2062                    for (; i < nDays; i++) {
2063                        mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2064                                : mUnfocusedMonthDateColor);
2065                        int x = (2 * i + 1) * mWidth / divisor;
2066                        canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
2067                    }
2068                }
2069            }
2070
2071            /**
2072             * Draws a horizontal line for separating the weeks.
2073             *
2074             * @param canvas The canvas to draw on.
2075             */
2076            private void drawWeekSeparators(Canvas canvas) {
2077                // If it is the topmost fully visible child do not draw separator line
2078                int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
2079                if (mListView.getChildAt(0).getTop() < 0) {
2080                    firstFullyVisiblePosition++;
2081                }
2082                if (firstFullyVisiblePosition == mWeek) {
2083                    return;
2084                }
2085                mDrawPaint.setColor(mWeekSeparatorLineColor);
2086                mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
2087                float startX;
2088                float stopX;
2089                if (isLayoutRtl()) {
2090                    startX = 0;
2091                    stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2092                } else {
2093                    startX = mShowWeekNumber ? mWidth / mNumCells : 0;
2094                    stopX = mWidth;
2095                }
2096                canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
2097            }
2098
2099            /**
2100             * Draws the selected date bars if this week has a selected day.
2101             *
2102             * @param canvas The canvas to draw on
2103             */
2104            private void drawSelectedDateVerticalBars(Canvas canvas) {
2105                if (!mHasSelectedDay) {
2106                    return;
2107                }
2108                mSelectedDateVerticalBar.setBounds(
2109                        mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
2110                        mWeekSeperatorLineWidth,
2111                        mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
2112                        mHeight);
2113                mSelectedDateVerticalBar.draw(canvas);
2114                mSelectedDateVerticalBar.setBounds(
2115                        mSelectedRight - mSelectedDateVerticalBarWidth / 2,
2116                        mWeekSeperatorLineWidth,
2117                        mSelectedRight + mSelectedDateVerticalBarWidth / 2,
2118                        mHeight);
2119                mSelectedDateVerticalBar.draw(canvas);
2120            }
2121
2122            @Override
2123            protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2124                mWidth = w;
2125                updateSelectionPositions();
2126            }
2127
2128            /**
2129             * This calculates the positions for the selected day lines.
2130             */
2131            private void updateSelectionPositions() {
2132                if (mHasSelectedDay) {
2133                    final boolean isLayoutRtl = isLayoutRtl();
2134                    int selectedPosition = mSelectedDay - mFirstDayOfWeek;
2135                    if (selectedPosition < 0) {
2136                        selectedPosition += 7;
2137                    }
2138                    if (mShowWeekNumber && !isLayoutRtl) {
2139                        selectedPosition++;
2140                    }
2141                    if (isLayoutRtl) {
2142                        mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
2143
2144                    } else {
2145                        mSelectedLeft = selectedPosition * mWidth / mNumCells;
2146                    }
2147                    mSelectedRight = mSelectedLeft + mWidth / mNumCells;
2148                }
2149            }
2150
2151            @Override
2152            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2153                mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
2154                        .getPaddingBottom()) / mShownWeekCount;
2155                setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
2156            }
2157        }
2158
2159    }
2160
2161}
2162