CalendarView.java revision 83ed4fe1d5ff00c2e1bd334c61f0acd3105a09ab
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.getResources().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            }
1644
1645            /**
1646             * Updates the selected day and related parameters.
1647             *
1648             * @param selectedDay The time to highlight
1649             */
1650            public void setSelectedDay(Calendar selectedDay) {
1651                if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
1652                        && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
1653                    return;
1654                }
1655                mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
1656                mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1657                mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
1658                notifyDataSetChanged();
1659            }
1660
1661            /**
1662             * @return The selected day of month.
1663             */
1664            public Calendar getSelectedDay() {
1665                return mSelectedDate;
1666            }
1667
1668            @Override
1669            public int getCount() {
1670                return mTotalWeekCount;
1671            }
1672
1673            @Override
1674            public Object getItem(int position) {
1675                return null;
1676            }
1677
1678            @Override
1679            public long getItemId(int position) {
1680                return position;
1681            }
1682
1683            @Override
1684            public View getView(int position, View convertView, ViewGroup parent) {
1685                WeekView weekView = null;
1686                if (convertView != null) {
1687                    weekView = (WeekView) convertView;
1688                } else {
1689                    weekView = new WeekView(mContext);
1690                    android.widget.AbsListView.LayoutParams params =
1691                            new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
1692                                    LayoutParams.WRAP_CONTENT);
1693                    weekView.setLayoutParams(params);
1694                    weekView.setClickable(true);
1695                    weekView.setOnTouchListener(this);
1696                }
1697
1698                int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1699                        Calendar.DAY_OF_WEEK) : -1;
1700                weekView.init(position, selectedWeekDay, mFocusedMonth);
1701
1702                return weekView;
1703            }
1704
1705            /**
1706             * Changes which month is in focus and updates the view.
1707             *
1708             * @param month The month to show as in focus [0-11]
1709             */
1710            public void setFocusMonth(int month) {
1711                if (mFocusedMonth == month) {
1712                    return;
1713                }
1714                mFocusedMonth = month;
1715                notifyDataSetChanged();
1716            }
1717
1718            @Override
1719            public boolean onTouch(View v, MotionEvent event) {
1720                if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
1721                    WeekView weekView = (WeekView) v;
1722                    // if we cannot find a day for the given location we are done
1723                    if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
1724                        return true;
1725                    }
1726                    // it is possible that the touched day is outside the valid range
1727                    // we draw whole weeks but range end can fall not on the week end
1728                    if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1729                        return true;
1730                    }
1731                    onDateTapped(mTempDate);
1732                    return true;
1733                }
1734                return false;
1735            }
1736
1737            /**
1738             * Maintains the same hour/min/sec but moves the day to the tapped day.
1739             *
1740             * @param day The day that was tapped
1741             */
1742            private void onDateTapped(Calendar day) {
1743                setSelectedDay(day);
1744                setMonthDisplayed(day);
1745            }
1746
1747            /**
1748             * This is here so we can identify single tap events and set the
1749             * selected day correctly
1750             */
1751            class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
1752                @Override
1753                public boolean onSingleTapUp(MotionEvent e) {
1754                    return true;
1755                }
1756            }
1757        }
1758
1759        /**
1760         * <p>
1761         * This is a dynamic view for drawing a single week. It can be configured to
1762         * display the week number, start the week on a given day, or show a reduced
1763         * number of days. It is intended for use as a single view within a
1764         * ListView. See {@link WeeksAdapter} for usage.
1765         * </p>
1766         */
1767        private class WeekView extends View {
1768
1769            private final Rect mTempRect = new Rect();
1770
1771            private final Paint mDrawPaint = new Paint();
1772
1773            private final Paint mMonthNumDrawPaint = new Paint();
1774
1775            // Cache the number strings so we don't have to recompute them each time
1776            private String[] mDayNumbers;
1777
1778            // Quick lookup for checking which days are in the focus month
1779            private boolean[] mFocusDay;
1780
1781            // Whether this view has a focused day.
1782            private boolean mHasFocusedDay;
1783
1784            // Whether this view has only focused days.
1785            private boolean mHasUnfocusedDay;
1786
1787            // The first day displayed by this item
1788            private Calendar mFirstDay;
1789
1790            // The month of the first day in this week
1791            private int mMonthOfFirstWeekDay = -1;
1792
1793            // The month of the last day in this week
1794            private int mLastWeekDayMonth = -1;
1795
1796            // The position of this week, equivalent to weeks since the week of Jan
1797            // 1st, 1900
1798            private int mWeek = -1;
1799
1800            // Quick reference to the width of this view, matches parent
1801            private int mWidth;
1802
1803            // The height this view should draw at in pixels, set by height param
1804            private int mHeight;
1805
1806            // If this view contains the selected day
1807            private boolean mHasSelectedDay = false;
1808
1809            // Which day is selected [0-6] or -1 if no day is selected
1810            private int mSelectedDay = -1;
1811
1812            // The number of days + a spot for week number if it is displayed
1813            private int mNumCells;
1814
1815            // The left edge of the selected day
1816            private int mSelectedLeft = -1;
1817
1818            // The right edge of the selected day
1819            private int mSelectedRight = -1;
1820
1821            public WeekView(Context context) {
1822                super(context);
1823
1824                // Sets up any standard paints that will be used
1825                initilaizePaints();
1826            }
1827
1828            /**
1829             * Initializes this week view.
1830             *
1831             * @param weekNumber The number of the week this view represents. The
1832             *            week number is a zero based index of the weeks since
1833             *            {@link CalendarView#getMinDate()}.
1834             * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
1835             *            selected day.
1836             * @param focusedMonth The month that is currently in focus i.e.
1837             *            highlighted.
1838             */
1839            public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
1840                mSelectedDay = selectedWeekDay;
1841                mHasSelectedDay = mSelectedDay != -1;
1842                mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
1843                mWeek = weekNumber;
1844                mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
1845
1846                mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1847                mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
1848
1849                // Allocate space for caching the day numbers and focus values
1850                mDayNumbers = new String[mNumCells];
1851                mFocusDay = new boolean[mNumCells];
1852
1853                // If we're showing the week number calculate it based on Monday
1854                int i = 0;
1855                if (mShowWeekNumber) {
1856                    mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1857                            mTempDate.get(Calendar.WEEK_OF_YEAR));
1858                    i++;
1859                }
1860
1861                // Now adjust our starting day based on the start day of the week
1862                int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
1863                mTempDate.add(Calendar.DAY_OF_MONTH, diff);
1864
1865                mFirstDay = (Calendar) mTempDate.clone();
1866                mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
1867
1868                mHasUnfocusedDay = true;
1869                for (; i < mNumCells; i++) {
1870                    final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
1871                    mFocusDay[i] = isFocusedDay;
1872                    mHasFocusedDay |= isFocusedDay;
1873                    mHasUnfocusedDay &= !isFocusedDay;
1874                    // do not draw dates outside the valid range to avoid user confusion
1875                    if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1876                        mDayNumbers[i] = "";
1877                    } else {
1878                        mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
1879                                mTempDate.get(Calendar.DAY_OF_MONTH));
1880                    }
1881                    mTempDate.add(Calendar.DAY_OF_MONTH, 1);
1882                }
1883                // We do one extra add at the end of the loop, if that pushed us to
1884                // new month undo it
1885                if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
1886                    mTempDate.add(Calendar.DAY_OF_MONTH, -1);
1887                }
1888                mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
1889
1890                updateSelectionPositions();
1891            }
1892
1893            /**
1894             * Initialize the paint instances.
1895             */
1896            private void initilaizePaints() {
1897                mDrawPaint.setFakeBoldText(false);
1898                mDrawPaint.setAntiAlias(true);
1899                mDrawPaint.setStyle(Style.FILL);
1900
1901                mMonthNumDrawPaint.setFakeBoldText(true);
1902                mMonthNumDrawPaint.setAntiAlias(true);
1903                mMonthNumDrawPaint.setStyle(Style.FILL);
1904                mMonthNumDrawPaint.setTextAlign(Align.CENTER);
1905                mMonthNumDrawPaint.setTextSize(mDateTextSize);
1906            }
1907
1908            /**
1909             * Returns the month of the first day in this week.
1910             *
1911             * @return The month the first day of this view is in.
1912             */
1913            public int getMonthOfFirstWeekDay() {
1914                return mMonthOfFirstWeekDay;
1915            }
1916
1917            /**
1918             * Returns the month of the last day in this week
1919             *
1920             * @return The month the last day of this view is in
1921             */
1922            public int getMonthOfLastWeekDay() {
1923                return mLastWeekDayMonth;
1924            }
1925
1926            /**
1927             * Returns the first day in this view.
1928             *
1929             * @return The first day in the view.
1930             */
1931            public Calendar getFirstDay() {
1932                return mFirstDay;
1933            }
1934
1935            /**
1936             * Calculates the day that the given x position is in, accounting for
1937             * week number.
1938             *
1939             * @param x The x position of the touch event.
1940             * @return True if a day was found for the given location.
1941             */
1942            public boolean getDayFromLocation(float x, Calendar outCalendar) {
1943                final boolean isLayoutRtl = isLayoutRtl();
1944
1945                int start;
1946                int end;
1947
1948                if (isLayoutRtl) {
1949                    start = 0;
1950                    end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1951                } else {
1952                    start = mShowWeekNumber ? mWidth / mNumCells : 0;
1953                    end = mWidth;
1954                }
1955
1956                if (x < start || x > end) {
1957                    outCalendar.clear();
1958                    return false;
1959                }
1960
1961                // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
1962                int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
1963
1964                if (isLayoutRtl) {
1965                    dayPosition = mDaysPerWeek - 1 - dayPosition;
1966                }
1967
1968                outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
1969                outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
1970
1971                return true;
1972            }
1973
1974            @Override
1975            protected void onDraw(Canvas canvas) {
1976                drawBackground(canvas);
1977                drawWeekNumbersAndDates(canvas);
1978                drawWeekSeparators(canvas);
1979                drawSelectedDateVerticalBars(canvas);
1980            }
1981
1982            /**
1983             * This draws the selection highlight if a day is selected in this week.
1984             *
1985             * @param canvas The canvas to draw on
1986             */
1987            private void drawBackground(Canvas canvas) {
1988                if (!mHasSelectedDay) {
1989                    return;
1990                }
1991                mDrawPaint.setColor(mSelectedWeekBackgroundColor);
1992
1993                mTempRect.top = mWeekSeperatorLineWidth;
1994                mTempRect.bottom = mHeight;
1995
1996                final boolean isLayoutRtl = isLayoutRtl();
1997
1998                if (isLayoutRtl) {
1999                    mTempRect.left = 0;
2000                    mTempRect.right = mSelectedLeft - 2;
2001                } else {
2002                    mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
2003                    mTempRect.right = mSelectedLeft - 2;
2004                }
2005                canvas.drawRect(mTempRect, mDrawPaint);
2006
2007                if (isLayoutRtl) {
2008                    mTempRect.left = mSelectedRight + 3;
2009                    mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2010                } else {
2011                    mTempRect.left = mSelectedRight + 3;
2012                    mTempRect.right = mWidth;
2013                }
2014                canvas.drawRect(mTempRect, mDrawPaint);
2015            }
2016
2017            /**
2018             * Draws the week and month day numbers for this week.
2019             *
2020             * @param canvas The canvas to draw on
2021             */
2022            private void drawWeekNumbersAndDates(Canvas canvas) {
2023                final float textHeight = mDrawPaint.getTextSize();
2024                final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
2025                final int nDays = mNumCells;
2026                final int divisor = 2 * nDays;
2027
2028                mDrawPaint.setTextAlign(Align.CENTER);
2029                mDrawPaint.setTextSize(mDateTextSize);
2030
2031                int i = 0;
2032
2033                if (isLayoutRtl()) {
2034                    for (; i < nDays - 1; i++) {
2035                        mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2036                                : mUnfocusedMonthDateColor);
2037                        int x = (2 * i + 1) * mWidth / divisor;
2038                        canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
2039                    }
2040                    if (mShowWeekNumber) {
2041                        mDrawPaint.setColor(mWeekNumberColor);
2042                        int x = mWidth - mWidth / divisor;
2043                        canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2044                    }
2045                } else {
2046                    if (mShowWeekNumber) {
2047                        mDrawPaint.setColor(mWeekNumberColor);
2048                        int x = mWidth / divisor;
2049                        canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2050                        i++;
2051                    }
2052                    for (; i < nDays; i++) {
2053                        mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2054                                : mUnfocusedMonthDateColor);
2055                        int x = (2 * i + 1) * mWidth / divisor;
2056                        canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
2057                    }
2058                }
2059            }
2060
2061            /**
2062             * Draws a horizontal line for separating the weeks.
2063             *
2064             * @param canvas The canvas to draw on.
2065             */
2066            private void drawWeekSeparators(Canvas canvas) {
2067                // If it is the topmost fully visible child do not draw separator line
2068                int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
2069                if (mListView.getChildAt(0).getTop() < 0) {
2070                    firstFullyVisiblePosition++;
2071                }
2072                if (firstFullyVisiblePosition == mWeek) {
2073                    return;
2074                }
2075                mDrawPaint.setColor(mWeekSeparatorLineColor);
2076                mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
2077                float startX;
2078                float stopX;
2079                if (isLayoutRtl()) {
2080                    startX = 0;
2081                    stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2082                } else {
2083                    startX = mShowWeekNumber ? mWidth / mNumCells : 0;
2084                    stopX = mWidth;
2085                }
2086                canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
2087            }
2088
2089            /**
2090             * Draws the selected date bars if this week has a selected day.
2091             *
2092             * @param canvas The canvas to draw on
2093             */
2094            private void drawSelectedDateVerticalBars(Canvas canvas) {
2095                if (!mHasSelectedDay) {
2096                    return;
2097                }
2098                mSelectedDateVerticalBar.setBounds(
2099                        mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
2100                        mWeekSeperatorLineWidth,
2101                        mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
2102                        mHeight);
2103                mSelectedDateVerticalBar.draw(canvas);
2104                mSelectedDateVerticalBar.setBounds(
2105                        mSelectedRight - mSelectedDateVerticalBarWidth / 2,
2106                        mWeekSeperatorLineWidth,
2107                        mSelectedRight + mSelectedDateVerticalBarWidth / 2,
2108                        mHeight);
2109                mSelectedDateVerticalBar.draw(canvas);
2110            }
2111
2112            @Override
2113            protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2114                mWidth = w;
2115                updateSelectionPositions();
2116            }
2117
2118            /**
2119             * This calculates the positions for the selected day lines.
2120             */
2121            private void updateSelectionPositions() {
2122                if (mHasSelectedDay) {
2123                    final boolean isLayoutRtl = isLayoutRtl();
2124                    int selectedPosition = mSelectedDay - mFirstDayOfWeek;
2125                    if (selectedPosition < 0) {
2126                        selectedPosition += 7;
2127                    }
2128                    if (mShowWeekNumber && !isLayoutRtl) {
2129                        selectedPosition++;
2130                    }
2131                    if (isLayoutRtl) {
2132                        mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
2133
2134                    } else {
2135                        mSelectedLeft = selectedPosition * mWidth / mNumCells;
2136                    }
2137                    mSelectedRight = mSelectedLeft + mWidth / mNumCells;
2138                }
2139            }
2140
2141            @Override
2142            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2143                mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
2144                        .getPaddingBottom()) / mShownWeekCount;
2145                setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
2146            }
2147        }
2148
2149    }
2150
2151}
2152