CalendarView.java revision ba4d2a08f910581ea2caa4efab5657e0ca9a40e4
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            // Find out which month we're moving into
1492            int month;
1493            if (mIsScrollingUp) {
1494                month = child.getMonthOfFirstWeekDay();
1495            } else {
1496                month = child.getMonthOfLastWeekDay();
1497            }
1498
1499            // And how it relates to our current highlighted month
1500            int monthDiff;
1501            if (mCurrentMonthDisplayed == 11 && month == 0) {
1502                monthDiff = 1;
1503            } else if (mCurrentMonthDisplayed == 0 && month == 11) {
1504                monthDiff = -1;
1505            } else {
1506                monthDiff = month - mCurrentMonthDisplayed;
1507            }
1508
1509            // Only switch months if we're scrolling away from the currently
1510            // selected month
1511            if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
1512                Calendar firstDay = child.getFirstDay();
1513                if (mIsScrollingUp) {
1514                    firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
1515                } else {
1516                    firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
1517                }
1518                setMonthDisplayed(firstDay);
1519            }
1520            mPreviousScrollPosition = currScroll;
1521            mPreviousScrollState = mCurrentScrollState;
1522        }
1523
1524        /**
1525         * Sets the month displayed at the top of this view based on time. Override
1526         * to add custom events when the title is changed.
1527         *
1528         * @param calendar A day in the new focus month.
1529         */
1530        private void setMonthDisplayed(Calendar calendar) {
1531            mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
1532            mAdapter.setFocusMonth(mCurrentMonthDisplayed);
1533            final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
1534                    | DateUtils.FORMAT_SHOW_YEAR;
1535            final long millis = calendar.getTimeInMillis();
1536            String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
1537            mMonthName.setText(newMonthName);
1538            mMonthName.invalidate();
1539        }
1540
1541        /**
1542         * @return Returns the number of weeks between the current <code>date</code>
1543         *         and the <code>mMinDate</code>.
1544         */
1545        private int getWeeksSinceMinDate(Calendar date) {
1546            if (date.before(mMinDate)) {
1547                throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
1548                        + " does not precede toDate: " + date.getTime());
1549            }
1550            long endTimeMillis = date.getTimeInMillis()
1551                    + date.getTimeZone().getOffset(date.getTimeInMillis());
1552            long startTimeMillis = mMinDate.getTimeInMillis()
1553                    + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
1554            long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
1555                    * MILLIS_IN_DAY;
1556            return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
1557        }
1558
1559        /**
1560         * Command responsible for acting upon scroll state changes.
1561         */
1562        private class ScrollStateRunnable implements Runnable {
1563            private AbsListView mView;
1564
1565            private int mNewState;
1566
1567            /**
1568             * Sets up the runnable with a short delay in case the scroll state
1569             * immediately changes again.
1570             *
1571             * @param view The list view that changed state
1572             * @param scrollState The new state it changed to
1573             */
1574            public void doScrollStateChange(AbsListView view, int scrollState) {
1575                mView = view;
1576                mNewState = scrollState;
1577                mDelegator.removeCallbacks(this);
1578                mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
1579            }
1580
1581            public void run() {
1582                mCurrentScrollState = mNewState;
1583                // Fix the position after a scroll or a fling ends
1584                if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
1585                        && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
1586                    View child = mView.getChildAt(0);
1587                    if (child == null) {
1588                        // The view is no longer visible, just return
1589                        return;
1590                    }
1591                    int dist = child.getBottom() - mListScrollTopOffset;
1592                    if (dist > mListScrollTopOffset) {
1593                        if (mIsScrollingUp) {
1594                            mView.smoothScrollBy(dist - child.getHeight(),
1595                                    ADJUSTMENT_SCROLL_DURATION);
1596                        } else {
1597                            mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
1598                        }
1599                    }
1600                }
1601                mPreviousScrollState = mNewState;
1602            }
1603        }
1604
1605        /**
1606         * <p>
1607         * This is a specialized adapter for creating a list of weeks with
1608         * selectable days. It can be configured to display the week number, start
1609         * the week on a given day, show a reduced number of days, or display an
1610         * arbitrary number of weeks at a time.
1611         * </p>
1612         */
1613        private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
1614
1615            private int mSelectedWeek;
1616
1617            private GestureDetector mGestureDetector;
1618
1619            private int mFocusedMonth;
1620
1621            private final Calendar mSelectedDate = Calendar.getInstance();
1622
1623            private int mTotalWeekCount;
1624
1625            public WeeksAdapter(Context context) {
1626                mContext = context;
1627                mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
1628                init();
1629            }
1630
1631            /**
1632             * Set up the gesture detector and selected time
1633             */
1634            private void init() {
1635                mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1636                mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
1637                if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
1638                        || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
1639                    mTotalWeekCount++;
1640                }
1641                notifyDataSetChanged();
1642            }
1643
1644            /**
1645             * Updates the selected day and related parameters.
1646             *
1647             * @param selectedDay The time to highlight
1648             */
1649            public void setSelectedDay(Calendar selectedDay) {
1650                if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
1651                        && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
1652                    return;
1653                }
1654                mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
1655                mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
1656                mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
1657                notifyDataSetChanged();
1658            }
1659
1660            /**
1661             * @return The selected day of month.
1662             */
1663            public Calendar getSelectedDay() {
1664                return mSelectedDate;
1665            }
1666
1667            @Override
1668            public int getCount() {
1669                return mTotalWeekCount;
1670            }
1671
1672            @Override
1673            public Object getItem(int position) {
1674                return null;
1675            }
1676
1677            @Override
1678            public long getItemId(int position) {
1679                return position;
1680            }
1681
1682            @Override
1683            public View getView(int position, View convertView, ViewGroup parent) {
1684                WeekView weekView = null;
1685                if (convertView != null) {
1686                    weekView = (WeekView) convertView;
1687                } else {
1688                    weekView = new WeekView(mContext);
1689                    android.widget.AbsListView.LayoutParams params =
1690                            new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
1691                                    LayoutParams.WRAP_CONTENT);
1692                    weekView.setLayoutParams(params);
1693                    weekView.setClickable(true);
1694                    weekView.setOnTouchListener(this);
1695                }
1696
1697                int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
1698                        Calendar.DAY_OF_WEEK) : -1;
1699                weekView.init(position, selectedWeekDay, mFocusedMonth);
1700
1701                return weekView;
1702            }
1703
1704            /**
1705             * Changes which month is in focus and updates the view.
1706             *
1707             * @param month The month to show as in focus [0-11]
1708             */
1709            public void setFocusMonth(int month) {
1710                if (mFocusedMonth == month) {
1711                    return;
1712                }
1713                mFocusedMonth = month;
1714                notifyDataSetChanged();
1715            }
1716
1717            @Override
1718            public boolean onTouch(View v, MotionEvent event) {
1719                if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
1720                    WeekView weekView = (WeekView) v;
1721                    // if we cannot find a day for the given location we are done
1722                    if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
1723                        return true;
1724                    }
1725                    // it is possible that the touched day is outside the valid range
1726                    // we draw whole weeks but range end can fall not on the week end
1727                    if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1728                        return true;
1729                    }
1730                    onDateTapped(mTempDate);
1731                    return true;
1732                }
1733                return false;
1734            }
1735
1736            /**
1737             * Maintains the same hour/min/sec but moves the day to the tapped day.
1738             *
1739             * @param day The day that was tapped
1740             */
1741            private void onDateTapped(Calendar day) {
1742                setSelectedDay(day);
1743                setMonthDisplayed(day);
1744            }
1745
1746            /**
1747             * This is here so we can identify single tap events and set the
1748             * selected day correctly
1749             */
1750            class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
1751                @Override
1752                public boolean onSingleTapUp(MotionEvent e) {
1753                    return true;
1754                }
1755            }
1756        }
1757
1758        /**
1759         * <p>
1760         * This is a dynamic view for drawing a single week. It can be configured to
1761         * display the week number, start the week on a given day, or show a reduced
1762         * number of days. It is intended for use as a single view within a
1763         * ListView. See {@link WeeksAdapter} for usage.
1764         * </p>
1765         */
1766        private class WeekView extends View {
1767
1768            private final Rect mTempRect = new Rect();
1769
1770            private final Paint mDrawPaint = new Paint();
1771
1772            private final Paint mMonthNumDrawPaint = new Paint();
1773
1774            // Cache the number strings so we don't have to recompute them each time
1775            private String[] mDayNumbers;
1776
1777            // Quick lookup for checking which days are in the focus month
1778            private boolean[] mFocusDay;
1779
1780            // Whether this view has a focused day.
1781            private boolean mHasFocusedDay;
1782
1783            // Whether this view has only focused days.
1784            private boolean mHasUnfocusedDay;
1785
1786            // The first day displayed by this item
1787            private Calendar mFirstDay;
1788
1789            // The month of the first day in this week
1790            private int mMonthOfFirstWeekDay = -1;
1791
1792            // The month of the last day in this week
1793            private int mLastWeekDayMonth = -1;
1794
1795            // The position of this week, equivalent to weeks since the week of Jan
1796            // 1st, 1900
1797            private int mWeek = -1;
1798
1799            // Quick reference to the width of this view, matches parent
1800            private int mWidth;
1801
1802            // The height this view should draw at in pixels, set by height param
1803            private int mHeight;
1804
1805            // If this view contains the selected day
1806            private boolean mHasSelectedDay = false;
1807
1808            // Which day is selected [0-6] or -1 if no day is selected
1809            private int mSelectedDay = -1;
1810
1811            // The number of days + a spot for week number if it is displayed
1812            private int mNumCells;
1813
1814            // The left edge of the selected day
1815            private int mSelectedLeft = -1;
1816
1817            // The right edge of the selected day
1818            private int mSelectedRight = -1;
1819
1820            public WeekView(Context context) {
1821                super(context);
1822
1823                // Sets up any standard paints that will be used
1824                initilaizePaints();
1825            }
1826
1827            /**
1828             * Initializes this week view.
1829             *
1830             * @param weekNumber The number of the week this view represents. The
1831             *            week number is a zero based index of the weeks since
1832             *            {@link CalendarView#getMinDate()}.
1833             * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
1834             *            selected day.
1835             * @param focusedMonth The month that is currently in focus i.e.
1836             *            highlighted.
1837             */
1838            public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
1839                mSelectedDay = selectedWeekDay;
1840                mHasSelectedDay = mSelectedDay != -1;
1841                mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
1842                mWeek = weekNumber;
1843                mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
1844
1845                mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
1846                mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
1847
1848                // Allocate space for caching the day numbers and focus values
1849                mDayNumbers = new String[mNumCells];
1850                mFocusDay = new boolean[mNumCells];
1851
1852                // If we're showing the week number calculate it based on Monday
1853                int i = 0;
1854                if (mShowWeekNumber) {
1855                    mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
1856                            mTempDate.get(Calendar.WEEK_OF_YEAR));
1857                    i++;
1858                }
1859
1860                // Now adjust our starting day based on the start day of the week
1861                int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
1862                mTempDate.add(Calendar.DAY_OF_MONTH, diff);
1863
1864                mFirstDay = (Calendar) mTempDate.clone();
1865                mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
1866
1867                mHasUnfocusedDay = true;
1868                for (; i < mNumCells; i++) {
1869                    final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
1870                    mFocusDay[i] = isFocusedDay;
1871                    mHasFocusedDay |= isFocusedDay;
1872                    mHasUnfocusedDay &= !isFocusedDay;
1873                    // do not draw dates outside the valid range to avoid user confusion
1874                    if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
1875                        mDayNumbers[i] = "";
1876                    } else {
1877                        mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
1878                                mTempDate.get(Calendar.DAY_OF_MONTH));
1879                    }
1880                    mTempDate.add(Calendar.DAY_OF_MONTH, 1);
1881                }
1882                // We do one extra add at the end of the loop, if that pushed us to
1883                // new month undo it
1884                if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
1885                    mTempDate.add(Calendar.DAY_OF_MONTH, -1);
1886                }
1887                mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
1888
1889                updateSelectionPositions();
1890            }
1891
1892            /**
1893             * Initialize the paint instances.
1894             */
1895            private void initilaizePaints() {
1896                mDrawPaint.setFakeBoldText(false);
1897                mDrawPaint.setAntiAlias(true);
1898                mDrawPaint.setStyle(Style.FILL);
1899
1900                mMonthNumDrawPaint.setFakeBoldText(true);
1901                mMonthNumDrawPaint.setAntiAlias(true);
1902                mMonthNumDrawPaint.setStyle(Style.FILL);
1903                mMonthNumDrawPaint.setTextAlign(Align.CENTER);
1904                mMonthNumDrawPaint.setTextSize(mDateTextSize);
1905            }
1906
1907            /**
1908             * Returns the month of the first day in this week.
1909             *
1910             * @return The month the first day of this view is in.
1911             */
1912            public int getMonthOfFirstWeekDay() {
1913                return mMonthOfFirstWeekDay;
1914            }
1915
1916            /**
1917             * Returns the month of the last day in this week
1918             *
1919             * @return The month the last day of this view is in
1920             */
1921            public int getMonthOfLastWeekDay() {
1922                return mLastWeekDayMonth;
1923            }
1924
1925            /**
1926             * Returns the first day in this view.
1927             *
1928             * @return The first day in the view.
1929             */
1930            public Calendar getFirstDay() {
1931                return mFirstDay;
1932            }
1933
1934            /**
1935             * Calculates the day that the given x position is in, accounting for
1936             * week number.
1937             *
1938             * @param x The x position of the touch event.
1939             * @return True if a day was found for the given location.
1940             */
1941            public boolean getDayFromLocation(float x, Calendar outCalendar) {
1942                final boolean isLayoutRtl = isLayoutRtl();
1943
1944                int start;
1945                int end;
1946
1947                if (isLayoutRtl) {
1948                    start = 0;
1949                    end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
1950                } else {
1951                    start = mShowWeekNumber ? mWidth / mNumCells : 0;
1952                    end = mWidth;
1953                }
1954
1955                if (x < start || x > end) {
1956                    outCalendar.clear();
1957                    return false;
1958                }
1959
1960                // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
1961                int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
1962
1963                if (isLayoutRtl) {
1964                    dayPosition = mDaysPerWeek - 1 - dayPosition;
1965                }
1966
1967                outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
1968                outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
1969
1970                return true;
1971            }
1972
1973            @Override
1974            protected void onDraw(Canvas canvas) {
1975                drawBackground(canvas);
1976                drawWeekNumbersAndDates(canvas);
1977                drawWeekSeparators(canvas);
1978                drawSelectedDateVerticalBars(canvas);
1979            }
1980
1981            /**
1982             * This draws the selection highlight if a day is selected in this week.
1983             *
1984             * @param canvas The canvas to draw on
1985             */
1986            private void drawBackground(Canvas canvas) {
1987                if (!mHasSelectedDay) {
1988                    return;
1989                }
1990                mDrawPaint.setColor(mSelectedWeekBackgroundColor);
1991
1992                mTempRect.top = mWeekSeperatorLineWidth;
1993                mTempRect.bottom = mHeight;
1994
1995                final boolean isLayoutRtl = isLayoutRtl();
1996
1997                if (isLayoutRtl) {
1998                    mTempRect.left = 0;
1999                    mTempRect.right = mSelectedLeft - 2;
2000                } else {
2001                    mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
2002                    mTempRect.right = mSelectedLeft - 2;
2003                }
2004                canvas.drawRect(mTempRect, mDrawPaint);
2005
2006                if (isLayoutRtl) {
2007                    mTempRect.left = mSelectedRight + 3;
2008                    mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2009                } else {
2010                    mTempRect.left = mSelectedRight + 3;
2011                    mTempRect.right = mWidth;
2012                }
2013                canvas.drawRect(mTempRect, mDrawPaint);
2014            }
2015
2016            /**
2017             * Draws the week and month day numbers for this week.
2018             *
2019             * @param canvas The canvas to draw on
2020             */
2021            private void drawWeekNumbersAndDates(Canvas canvas) {
2022                final float textHeight = mDrawPaint.getTextSize();
2023                final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
2024                final int nDays = mNumCells;
2025                final int divisor = 2 * nDays;
2026
2027                mDrawPaint.setTextAlign(Align.CENTER);
2028                mDrawPaint.setTextSize(mDateTextSize);
2029
2030                int i = 0;
2031
2032                if (isLayoutRtl()) {
2033                    for (; i < nDays - 1; i++) {
2034                        mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2035                                : mUnfocusedMonthDateColor);
2036                        int x = (2 * i + 1) * mWidth / divisor;
2037                        canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
2038                    }
2039                    if (mShowWeekNumber) {
2040                        mDrawPaint.setColor(mWeekNumberColor);
2041                        int x = mWidth - mWidth / divisor;
2042                        canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2043                    }
2044                } else {
2045                    if (mShowWeekNumber) {
2046                        mDrawPaint.setColor(mWeekNumberColor);
2047                        int x = mWidth / divisor;
2048                        canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
2049                        i++;
2050                    }
2051                    for (; i < nDays; i++) {
2052                        mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
2053                                : mUnfocusedMonthDateColor);
2054                        int x = (2 * i + 1) * mWidth / divisor;
2055                        canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
2056                    }
2057                }
2058            }
2059
2060            /**
2061             * Draws a horizontal line for separating the weeks.
2062             *
2063             * @param canvas The canvas to draw on.
2064             */
2065            private void drawWeekSeparators(Canvas canvas) {
2066                // If it is the topmost fully visible child do not draw separator line
2067                int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
2068                if (mListView.getChildAt(0).getTop() < 0) {
2069                    firstFullyVisiblePosition++;
2070                }
2071                if (firstFullyVisiblePosition == mWeek) {
2072                    return;
2073                }
2074                mDrawPaint.setColor(mWeekSeparatorLineColor);
2075                mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
2076                float startX;
2077                float stopX;
2078                if (isLayoutRtl()) {
2079                    startX = 0;
2080                    stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
2081                } else {
2082                    startX = mShowWeekNumber ? mWidth / mNumCells : 0;
2083                    stopX = mWidth;
2084                }
2085                canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
2086            }
2087
2088            /**
2089             * Draws the selected date bars if this week has a selected day.
2090             *
2091             * @param canvas The canvas to draw on
2092             */
2093            private void drawSelectedDateVerticalBars(Canvas canvas) {
2094                if (!mHasSelectedDay) {
2095                    return;
2096                }
2097                mSelectedDateVerticalBar.setBounds(
2098                        mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
2099                        mWeekSeperatorLineWidth,
2100                        mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
2101                        mHeight);
2102                mSelectedDateVerticalBar.draw(canvas);
2103                mSelectedDateVerticalBar.setBounds(
2104                        mSelectedRight - mSelectedDateVerticalBarWidth / 2,
2105                        mWeekSeperatorLineWidth,
2106                        mSelectedRight + mSelectedDateVerticalBarWidth / 2,
2107                        mHeight);
2108                mSelectedDateVerticalBar.draw(canvas);
2109            }
2110
2111            @Override
2112            protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2113                mWidth = w;
2114                updateSelectionPositions();
2115            }
2116
2117            /**
2118             * This calculates the positions for the selected day lines.
2119             */
2120            private void updateSelectionPositions() {
2121                if (mHasSelectedDay) {
2122                    final boolean isLayoutRtl = isLayoutRtl();
2123                    int selectedPosition = mSelectedDay - mFirstDayOfWeek;
2124                    if (selectedPosition < 0) {
2125                        selectedPosition += 7;
2126                    }
2127                    if (mShowWeekNumber && !isLayoutRtl) {
2128                        selectedPosition++;
2129                    }
2130                    if (isLayoutRtl) {
2131                        mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
2132
2133                    } else {
2134                        mSelectedLeft = selectedPosition * mWidth / mNumCells;
2135                    }
2136                    mSelectedRight = mSelectedLeft + mWidth / mNumCells;
2137                }
2138            }
2139
2140            @Override
2141            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2142                mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
2143                        .getPaddingBottom()) / mShownWeekCount;
2144                setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
2145            }
2146        }
2147
2148    }
2149
2150}
2151