CalendarView.java revision 8eea3ea5591e59f55cbb4f6b2b7e9363a285ced3
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 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();
930        }
931
932        @Override
933        public void setShownWeekCount(int count) {
934            if (mShownWeekCount != count) {
935                mShownWeekCount = count;
936                mDelegator.invalidate();
937            }
938        }
939
940        @Override
941        public int getShownWeekCount() {
942            return mShownWeekCount;
943        }
944
945        @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();
954                    }
955                }
956            }
957        }
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) {
1032            Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
1033            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) {
1318                label.setVisibility(View.VISIBLE);
1319            } else {
1320                label.setVisibility(View.GONE);
1321            }
1322            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                }
1333            }
1334            mDayNamesHeader.invalidate();
1335        }
1336
1337        /**
1338         * Sets all the required fields for the list view.
1339         */
1340        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);
1360        }
1361
1362        /**
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);
1415                }
1416
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
1491            if (child != null) {
1492                // Find out which month we're moving into
1493                int month;
1494                if (mIsScrollingUp) {
1495                    month = child.getMonthOfFirstWeekDay();
1496                } else {
1497                    month = child.getMonthOfLastWeekDay();
1498                }
1499
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                }
1521            }
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                        }
1601                    }
1602                }
1603                mPreviousScrollState = mNewState;
1604            }
1605        }
1606
1607        /**
1608         * <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>
1614         */
1615        private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
1616
1617            private int mSelectedWeek;
1618
1619            private GestureDetector mGestureDetector;
1620
1621            private int mFocusedMonth;
1622
1623            private final Calendar mSelectedDate = Calendar.getInstance();
1624
1625            private int mTotalWeekCount;
1626
1627            public WeeksAdapter(Context context) {
1628                mContext = context;
1629                mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
1630                init();
1631            }
1632
1633            /**
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++;
1642                }
1643                notifyDataSetChanged();
1644            }
1645
1646            /**
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            }
1661
1662            /**
1663             * @return The selected day of month.
1664             */
1665            public Calendar getSelectedDay() {
1666                return mSelectedDate;
1667            }
1668
1669            @Override
1670            public int getCount() {
1671                return mTotalWeekCount;
1672            }
1673
1674            @Override
1675            public Object getItem(int position) {
1676                return null;
1677            }
1678
1679            @Override
1680            public long getItemId(int position) {
1681                return position;
1682            }
1683
1684            @Override
1685            public View getView(int position, View convertView, ViewGroup parent) {
1686                WeekView weekView = null;
1687                if (convertView != null) {
1688                    weekView = (WeekView) convertView;
1689                } else {
1690                    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);
1697                }
1698
1699                int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1700                        Calendar.DAY_OF_WEEK) : -1;
1701                weekView.init(position, selectedWeekDay, mFocusedMonth);
1702
1703                return weekView;
1704            }
1705
1706            /**
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                }
1735                return false;
1736            }
1737
1738            /**
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);
1746            }
1747
1748            /**
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            }
1758        }
1759
1760        /**
1761         * <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>
1767         */
1768        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();
1827            }
1828
1829            /**
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());
1846
1847                mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1848                mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
1849
1850                // Allocate space for caching the day numbers and focus values
1851                mDayNumbers = new String[mNumCells];
1852                mFocusDay = new boolean[mNumCells];
1853
1854                // If we're showing the week number calculate it based on Monday
1855                int i = 0;
1856                if (mShowWeekNumber) {
1857                    mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1858                            mTempDate.get(Calendar.WEEK_OF_YEAR));
1859                    i++;
1860                }
1861
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);
1883                }
1884                // 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);
1890
1891                updateSelectionPositions();
1892            }
1893
1894            /**
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);
1907            }
1908
1909            /**
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            }
1917
1918            /**
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) {
1944                final boolean isLayoutRtl = isLayoutRtl();
1945
1946                int start;
1947                int end;
1948
1949                if (isLayoutRtl) {
1950                    start = 0;
1951                    end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1952                } else {
1953                    start = mShowWeekNumber ? mWidth / mNumCells : 0;
1954                    end = mWidth;
1955                }
1956
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);
2147            }
2148        }
2149
2150    }
2151
2152}
2153