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