Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 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 com.android.internal.R;
     20 
     21 import android.app.Service;
     22 import android.content.Context;
     23 import android.content.res.Configuration;
     24 import android.content.res.TypedArray;
     25 import android.database.DataSetObserver;
     26 import android.graphics.Canvas;
     27 import android.graphics.Paint;
     28 import android.graphics.Rect;
     29 import android.graphics.drawable.Drawable;
     30 import android.text.TextUtils;
     31 import android.text.format.DateUtils;
     32 import android.util.AttributeSet;
     33 import android.util.DisplayMetrics;
     34 import android.util.TypedValue;
     35 import android.view.GestureDetector;
     36 import android.view.LayoutInflater;
     37 import android.view.MotionEvent;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 
     41 import java.util.Calendar;
     42 import java.util.Locale;
     43 
     44 import libcore.icu.LocaleData;
     45 
     46 /**
     47  * A delegate implementing the legacy CalendarView
     48  */
     49 class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate {
     50     /**
     51      * Default value whether to show week number.
     52      */
     53     private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
     54 
     55     /**
     56      * The number of milliseconds in a day.e
     57      */
     58     private static final long MILLIS_IN_DAY = 86400000L;
     59 
     60     /**
     61      * The number of day in a week.
     62      */
     63     private static final int DAYS_PER_WEEK = 7;
     64 
     65     /**
     66      * The number of milliseconds in a week.
     67      */
     68     private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
     69 
     70     /**
     71      * Affects when the month selection will change while scrolling upe
     72      */
     73     private static final int SCROLL_HYST_WEEKS = 2;
     74 
     75     /**
     76      * How long the GoTo fling animation should last.
     77      */
     78     private static final int GOTO_SCROLL_DURATION = 1000;
     79 
     80     /**
     81      * The duration of the adjustment upon a user scroll in milliseconds.
     82      */
     83     private static final int ADJUSTMENT_SCROLL_DURATION = 500;
     84 
     85     /**
     86      * How long to wait after receiving an onScrollStateChanged notification
     87      * before acting on it.
     88      */
     89     private static final int SCROLL_CHANGE_DELAY = 40;
     90 
     91     private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
     92 
     93     private static final int DEFAULT_DATE_TEXT_SIZE = 14;
     94 
     95     private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
     96 
     97     private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
     98 
     99     private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
    100 
    101     private static final int UNSCALED_BOTTOM_BUFFER = 20;
    102 
    103     private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
    104 
    105     private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
    106 
    107     private final int mWeekSeperatorLineWidth;
    108 
    109     private int mDateTextSize;
    110 
    111     private Drawable mSelectedDateVerticalBar;
    112 
    113     private final int mSelectedDateVerticalBarWidth;
    114 
    115     private int mSelectedWeekBackgroundColor;
    116 
    117     private int mFocusedMonthDateColor;
    118 
    119     private int mUnfocusedMonthDateColor;
    120 
    121     private int mWeekSeparatorLineColor;
    122 
    123     private int mWeekNumberColor;
    124 
    125     private int mWeekDayTextAppearanceResId;
    126 
    127     private int mDateTextAppearanceResId;
    128 
    129     /**
    130      * The top offset of the weeks list.
    131      */
    132     private int mListScrollTopOffset = 2;
    133 
    134     /**
    135      * The visible height of a week view.
    136      */
    137     private int mWeekMinVisibleHeight = 12;
    138 
    139     /**
    140      * The visible height of a week view.
    141      */
    142     private int mBottomBuffer = 20;
    143 
    144     /**
    145      * The number of shown weeks.
    146      */
    147     private int mShownWeekCount;
    148 
    149     /**
    150      * Flag whether to show the week number.
    151      */
    152     private boolean mShowWeekNumber;
    153 
    154     /**
    155      * The number of day per week to be shown.
    156      */
    157     private int mDaysPerWeek = 7;
    158 
    159     /**
    160      * The friction of the week list while flinging.
    161      */
    162     private float mFriction = .05f;
    163 
    164     /**
    165      * Scale for adjusting velocity of the week list while flinging.
    166      */
    167     private float mVelocityScale = 0.333f;
    168 
    169     /**
    170      * The adapter for the weeks list.
    171      */
    172     private WeeksAdapter mAdapter;
    173 
    174     /**
    175      * The weeks list.
    176      */
    177     private ListView mListView;
    178 
    179     /**
    180      * The name of the month to display.
    181      */
    182     private TextView mMonthName;
    183 
    184     /**
    185      * The header with week day names.
    186      */
    187     private ViewGroup mDayNamesHeader;
    188 
    189     /**
    190      * Cached abbreviations for day of week names.
    191      */
    192     private String[] mDayNamesShort;
    193 
    194     /**
    195      * Cached full-length day of week names.
    196      */
    197     private String[] mDayNamesLong;
    198 
    199     /**
    200      * The first day of the week.
    201      */
    202     private int mFirstDayOfWeek;
    203 
    204     /**
    205      * Which month should be displayed/highlighted [0-11].
    206      */
    207     private int mCurrentMonthDisplayed = -1;
    208 
    209     /**
    210      * Used for tracking during a scroll.
    211      */
    212     private long mPreviousScrollPosition;
    213 
    214     /**
    215      * Used for tracking which direction the view is scrolling.
    216      */
    217     private boolean mIsScrollingUp = false;
    218 
    219     /**
    220      * The previous scroll state of the weeks ListView.
    221      */
    222     private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
    223 
    224     /**
    225      * The current scroll state of the weeks ListView.
    226      */
    227     private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
    228 
    229     /**
    230      * Listener for changes in the selected day.
    231      */
    232     private CalendarView.OnDateChangeListener mOnDateChangeListener;
    233 
    234     /**
    235      * Command for adjusting the position after a scroll/fling.
    236      */
    237     private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
    238 
    239     /**
    240      * Temporary instance to avoid multiple instantiations.
    241      */
    242     private Calendar mTempDate;
    243 
    244     /**
    245      * The first day of the focused month.
    246      */
    247     private Calendar mFirstDayOfMonth;
    248 
    249     /**
    250      * The start date of the range supported by this picker.
    251      */
    252     private Calendar mMinDate;
    253 
    254     /**
    255      * The end date of the range supported by this picker.
    256      */
    257     private Calendar mMaxDate;
    258 
    259     CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs,
    260             int defStyleAttr, int defStyleRes) {
    261         super(delegator, context);
    262 
    263         final TypedArray a = context.obtainStyledAttributes(attrs,
    264                 R.styleable.CalendarView, defStyleAttr, defStyleRes);
    265         mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber,
    266                 DEFAULT_SHOW_WEEK_NUMBER);
    267         mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
    268                 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
    269         final String minDate = a.getString(R.styleable.CalendarView_minDate);
    270         if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
    271             parseDate(DEFAULT_MIN_DATE, mMinDate);
    272         }
    273         final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
    274         if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
    275             parseDate(DEFAULT_MAX_DATE, mMaxDate);
    276         }
    277         if (mMaxDate.before(mMinDate)) {
    278             throw new IllegalArgumentException("Max date cannot be before min date.");
    279         }
    280         mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount,
    281                 DEFAULT_SHOWN_WEEK_COUNT);
    282         mSelectedWeekBackgroundColor = a.getColor(
    283                 R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
    284         mFocusedMonthDateColor = a.getColor(
    285                 R.styleable.CalendarView_focusedMonthDateColor, 0);
    286         mUnfocusedMonthDateColor = a.getColor(
    287                 R.styleable.CalendarView_unfocusedMonthDateColor, 0);
    288         mWeekSeparatorLineColor = a.getColor(
    289                 R.styleable.CalendarView_weekSeparatorLineColor, 0);
    290         mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0);
    291         mSelectedDateVerticalBar = a.getDrawable(
    292                 R.styleable.CalendarView_selectedDateVerticalBar);
    293 
    294         mDateTextAppearanceResId = a.getResourceId(
    295                 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
    296         updateDateTextSize();
    297 
    298         mWeekDayTextAppearanceResId = a.getResourceId(
    299                 R.styleable.CalendarView_weekDayTextAppearance,
    300                 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
    301         a.recycle();
    302 
    303         DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
    304         mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    305                 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
    306         mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    307                 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
    308         mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    309                 UNSCALED_BOTTOM_BUFFER, displayMetrics);
    310         mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    311                 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
    312         mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    313                 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
    314 
    315         LayoutInflater layoutInflater = (LayoutInflater) mContext
    316                 .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
    317         View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
    318         mDelegator.addView(content);
    319 
    320         mListView = (ListView) mDelegator.findViewById(R.id.list);
    321         mDayNamesHeader = (ViewGroup) content.findViewById(R.id.day_names);
    322         mMonthName = (TextView) content.findViewById(R.id.month_name);
    323 
    324         setUpHeader();
    325         setUpListView();
    326         setUpAdapter();
    327 
    328         // go to today or whichever is close to today min or max date
    329         mTempDate.setTimeInMillis(System.currentTimeMillis());
    330         if (mTempDate.before(mMinDate)) {
    331             goTo(mMinDate, false, true, true);
    332         } else if (mMaxDate.before(mTempDate)) {
    333             goTo(mMaxDate, false, true, true);
    334         } else {
    335             goTo(mTempDate, false, true, true);
    336         }
    337 
    338         mDelegator.invalidate();
    339     }
    340 
    341     @Override
    342     public void setShownWeekCount(int count) {
    343         if (mShownWeekCount != count) {
    344             mShownWeekCount = count;
    345             mDelegator.invalidate();
    346         }
    347     }
    348 
    349     @Override
    350     public int getShownWeekCount() {
    351         return mShownWeekCount;
    352     }
    353 
    354     @Override
    355     public void setSelectedWeekBackgroundColor(int color) {
    356         if (mSelectedWeekBackgroundColor != color) {
    357             mSelectedWeekBackgroundColor = color;
    358             final int childCount = mListView.getChildCount();
    359             for (int i = 0; i < childCount; i++) {
    360                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    361                 if (weekView.mHasSelectedDay) {
    362                     weekView.invalidate();
    363                 }
    364             }
    365         }
    366     }
    367 
    368     @Override
    369     public int getSelectedWeekBackgroundColor() {
    370         return mSelectedWeekBackgroundColor;
    371     }
    372 
    373     @Override
    374     public void setFocusedMonthDateColor(int color) {
    375         if (mFocusedMonthDateColor != color) {
    376             mFocusedMonthDateColor = color;
    377             final int childCount = mListView.getChildCount();
    378             for (int i = 0; i < childCount; i++) {
    379                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    380                 if (weekView.mHasFocusedDay) {
    381                     weekView.invalidate();
    382                 }
    383             }
    384         }
    385     }
    386 
    387     @Override
    388     public int getFocusedMonthDateColor() {
    389         return mFocusedMonthDateColor;
    390     }
    391 
    392     @Override
    393     public void setUnfocusedMonthDateColor(int color) {
    394         if (mUnfocusedMonthDateColor != color) {
    395             mUnfocusedMonthDateColor = color;
    396             final int childCount = mListView.getChildCount();
    397             for (int i = 0; i < childCount; i++) {
    398                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    399                 if (weekView.mHasUnfocusedDay) {
    400                     weekView.invalidate();
    401                 }
    402             }
    403         }
    404     }
    405 
    406     @Override
    407     public int getUnfocusedMonthDateColor() {
    408         return mFocusedMonthDateColor;
    409     }
    410 
    411     @Override
    412     public void setWeekNumberColor(int color) {
    413         if (mWeekNumberColor != color) {
    414             mWeekNumberColor = color;
    415             if (mShowWeekNumber) {
    416                 invalidateAllWeekViews();
    417             }
    418         }
    419     }
    420 
    421     @Override
    422     public int getWeekNumberColor() {
    423         return mWeekNumberColor;
    424     }
    425 
    426     @Override
    427     public void setWeekSeparatorLineColor(int color) {
    428         if (mWeekSeparatorLineColor != color) {
    429             mWeekSeparatorLineColor = color;
    430             invalidateAllWeekViews();
    431         }
    432     }
    433 
    434     @Override
    435     public int getWeekSeparatorLineColor() {
    436         return mWeekSeparatorLineColor;
    437     }
    438 
    439     @Override
    440     public void setSelectedDateVerticalBar(int resourceId) {
    441         Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
    442         setSelectedDateVerticalBar(drawable);
    443     }
    444 
    445     @Override
    446     public void setSelectedDateVerticalBar(Drawable drawable) {
    447         if (mSelectedDateVerticalBar != drawable) {
    448             mSelectedDateVerticalBar = drawable;
    449             final int childCount = mListView.getChildCount();
    450             for (int i = 0; i < childCount; i++) {
    451                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    452                 if (weekView.mHasSelectedDay) {
    453                     weekView.invalidate();
    454                 }
    455             }
    456         }
    457     }
    458 
    459     @Override
    460     public Drawable getSelectedDateVerticalBar() {
    461         return mSelectedDateVerticalBar;
    462     }
    463 
    464     @Override
    465     public void setWeekDayTextAppearance(int resourceId) {
    466         if (mWeekDayTextAppearanceResId != resourceId) {
    467             mWeekDayTextAppearanceResId = resourceId;
    468             setUpHeader();
    469         }
    470     }
    471 
    472     @Override
    473     public int getWeekDayTextAppearance() {
    474         return mWeekDayTextAppearanceResId;
    475     }
    476 
    477     @Override
    478     public void setDateTextAppearance(int resourceId) {
    479         if (mDateTextAppearanceResId != resourceId) {
    480             mDateTextAppearanceResId = resourceId;
    481             updateDateTextSize();
    482             invalidateAllWeekViews();
    483         }
    484     }
    485 
    486     @Override
    487     public int getDateTextAppearance() {
    488         return mDateTextAppearanceResId;
    489     }
    490 
    491     @Override
    492     public void setMinDate(long minDate) {
    493         mTempDate.setTimeInMillis(minDate);
    494         if (isSameDate(mTempDate, mMinDate)) {
    495             return;
    496         }
    497         mMinDate.setTimeInMillis(minDate);
    498         // make sure the current date is not earlier than
    499         // the new min date since the latter is used for
    500         // calculating the indices in the adapter thus
    501         // avoiding out of bounds error
    502         Calendar date = mAdapter.mSelectedDate;
    503         if (date.before(mMinDate)) {
    504             mAdapter.setSelectedDay(mMinDate);
    505         }
    506         // reinitialize the adapter since its range depends on min date
    507         mAdapter.init();
    508         if (date.before(mMinDate)) {
    509             setDate(mTempDate.getTimeInMillis());
    510         } else {
    511             // we go to the current date to force the ListView to query its
    512             // adapter for the shown views since we have changed the adapter
    513             // range and the base from which the later calculates item indices
    514             // note that calling setDate will not work since the date is the same
    515             goTo(date, false, true, false);
    516         }
    517     }
    518 
    519     @Override
    520     public long getMinDate() {
    521         return mMinDate.getTimeInMillis();
    522     }
    523 
    524     @Override
    525     public void setMaxDate(long maxDate) {
    526         mTempDate.setTimeInMillis(maxDate);
    527         if (isSameDate(mTempDate, mMaxDate)) {
    528             return;
    529         }
    530         mMaxDate.setTimeInMillis(maxDate);
    531         // reinitialize the adapter since its range depends on max date
    532         mAdapter.init();
    533         Calendar date = mAdapter.mSelectedDate;
    534         if (date.after(mMaxDate)) {
    535             setDate(mMaxDate.getTimeInMillis());
    536         } else {
    537             // we go to the current date to force the ListView to query its
    538             // adapter for the shown views since we have changed the adapter
    539             // range and the base from which the later calculates item indices
    540             // note that calling setDate will not work since the date is the same
    541             goTo(date, false, true, false);
    542         }
    543     }
    544 
    545     @Override
    546     public long getMaxDate() {
    547         return mMaxDate.getTimeInMillis();
    548     }
    549 
    550     @Override
    551     public void setShowWeekNumber(boolean showWeekNumber) {
    552         if (mShowWeekNumber == showWeekNumber) {
    553             return;
    554         }
    555         mShowWeekNumber = showWeekNumber;
    556         mAdapter.notifyDataSetChanged();
    557         setUpHeader();
    558     }
    559 
    560     @Override
    561     public boolean getShowWeekNumber() {
    562         return mShowWeekNumber;
    563     }
    564 
    565     @Override
    566     public void setFirstDayOfWeek(int firstDayOfWeek) {
    567         if (mFirstDayOfWeek == firstDayOfWeek) {
    568             return;
    569         }
    570         mFirstDayOfWeek = firstDayOfWeek;
    571         mAdapter.init();
    572         mAdapter.notifyDataSetChanged();
    573         setUpHeader();
    574     }
    575 
    576     @Override
    577     public int getFirstDayOfWeek() {
    578         return mFirstDayOfWeek;
    579     }
    580 
    581     @Override
    582     public void setDate(long date) {
    583         setDate(date, false, false);
    584     }
    585 
    586     @Override
    587     public void setDate(long date, boolean animate, boolean center) {
    588         mTempDate.setTimeInMillis(date);
    589         if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
    590             return;
    591         }
    592         goTo(mTempDate, animate, true, center);
    593     }
    594 
    595     @Override
    596     public long getDate() {
    597         return mAdapter.mSelectedDate.getTimeInMillis();
    598     }
    599 
    600     @Override
    601     public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) {
    602         mOnDateChangeListener = listener;
    603     }
    604 
    605     @Override
    606     public void onConfigurationChanged(Configuration newConfig) {
    607         setCurrentLocale(newConfig.locale);
    608     }
    609 
    610     /**
    611      * Sets the current locale.
    612      *
    613      * @param locale The current locale.
    614      */
    615     @Override
    616     protected void setCurrentLocale(Locale locale) {
    617         super.setCurrentLocale(locale);
    618 
    619         mTempDate = getCalendarForLocale(mTempDate, locale);
    620         mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
    621         mMinDate = getCalendarForLocale(mMinDate, locale);
    622         mMaxDate = getCalendarForLocale(mMaxDate, locale);
    623     }
    624     private void updateDateTextSize() {
    625         TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
    626                 mDateTextAppearanceResId, R.styleable.TextAppearance);
    627         mDateTextSize = dateTextAppearance.getDimensionPixelSize(
    628                 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
    629         dateTextAppearance.recycle();
    630     }
    631 
    632     /**
    633      * Invalidates all week views.
    634      */
    635     private void invalidateAllWeekViews() {
    636         final int childCount = mListView.getChildCount();
    637         for (int i = 0; i < childCount; i++) {
    638             View view = mListView.getChildAt(i);
    639             view.invalidate();
    640         }
    641     }
    642 
    643     /**
    644      * Gets a calendar for locale bootstrapped with the value of a given calendar.
    645      *
    646      * @param oldCalendar The old calendar.
    647      * @param locale The locale.
    648      */
    649     private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
    650         if (oldCalendar == null) {
    651             return Calendar.getInstance(locale);
    652         } else {
    653             final long currentTimeMillis = oldCalendar.getTimeInMillis();
    654             Calendar newCalendar = Calendar.getInstance(locale);
    655             newCalendar.setTimeInMillis(currentTimeMillis);
    656             return newCalendar;
    657         }
    658     }
    659 
    660     /**
    661      * @return True if the <code>firstDate</code> is the same as the <code>
    662      * secondDate</code>.
    663      */
    664     private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
    665         return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
    666                 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
    667     }
    668 
    669     /**
    670      * Creates a new adapter if necessary and sets up its parameters.
    671      */
    672     private void setUpAdapter() {
    673         if (mAdapter == null) {
    674             mAdapter = new WeeksAdapter(mContext);
    675             mAdapter.registerDataSetObserver(new DataSetObserver() {
    676                 @Override
    677                 public void onChanged() {
    678                     if (mOnDateChangeListener != null) {
    679                         Calendar selectedDay = mAdapter.getSelectedDay();
    680                         mOnDateChangeListener.onSelectedDayChange(mDelegator,
    681                                 selectedDay.get(Calendar.YEAR),
    682                                 selectedDay.get(Calendar.MONTH),
    683                                 selectedDay.get(Calendar.DAY_OF_MONTH));
    684                     }
    685                 }
    686             });
    687             mListView.setAdapter(mAdapter);
    688         }
    689 
    690         // refresh the view with the new parameters
    691         mAdapter.notifyDataSetChanged();
    692     }
    693 
    694     /**
    695      * Sets up the strings to be used by the header.
    696      */
    697     private void setUpHeader() {
    698         mDayNamesShort = new String[mDaysPerWeek];
    699         mDayNamesLong = new String[mDaysPerWeek];
    700         for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
    701             int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
    702             mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
    703                     DateUtils.LENGTH_SHORTEST);
    704             mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
    705                     DateUtils.LENGTH_LONG);
    706         }
    707 
    708         TextView label = (TextView) mDayNamesHeader.getChildAt(0);
    709         if (mShowWeekNumber) {
    710             label.setVisibility(View.VISIBLE);
    711         } else {
    712             label.setVisibility(View.GONE);
    713         }
    714         for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
    715             label = (TextView) mDayNamesHeader.getChildAt(i);
    716             if (mWeekDayTextAppearanceResId > -1) {
    717                 label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
    718             }
    719             if (i < mDaysPerWeek + 1) {
    720                 label.setText(mDayNamesShort[i - 1]);
    721                 label.setContentDescription(mDayNamesLong[i - 1]);
    722                 label.setVisibility(View.VISIBLE);
    723             } else {
    724                 label.setVisibility(View.GONE);
    725             }
    726         }
    727         mDayNamesHeader.invalidate();
    728     }
    729 
    730     /**
    731      * Sets all the required fields for the list view.
    732      */
    733     private void setUpListView() {
    734         // Configure the listview
    735         mListView.setDivider(null);
    736         mListView.setItemsCanFocus(true);
    737         mListView.setVerticalScrollBarEnabled(false);
    738         mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
    739             public void onScrollStateChanged(AbsListView view, int scrollState) {
    740                 CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState);
    741             }
    742 
    743             public void onScroll(
    744                     AbsListView view, int firstVisibleItem, int visibleItemCount,
    745                     int totalItemCount) {
    746                 CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem,
    747                         visibleItemCount, totalItemCount);
    748             }
    749         });
    750         // Make the scrolling behavior nicer
    751         mListView.setFriction(mFriction);
    752         mListView.setVelocityScale(mVelocityScale);
    753     }
    754 
    755     /**
    756      * This moves to the specified time in the view. If the time is not already
    757      * in range it will move the list so that the first of the month containing
    758      * the time is at the top of the view. If the new time is already in view
    759      * the list will not be scrolled unless forceScroll is true. This time may
    760      * optionally be highlighted as selected as well.
    761      *
    762      * @param date The time to move to.
    763      * @param animate Whether to scroll to the given time or just redraw at the
    764      *            new location.
    765      * @param setSelected Whether to set the given time as selected.
    766      * @param forceScroll Whether to recenter even if the time is already
    767      *            visible.
    768      *
    769      * @throws IllegalArgumentException of the provided date is before the
    770      *        range start of after the range end.
    771      */
    772     private void goTo(Calendar date, boolean animate, boolean setSelected,
    773             boolean forceScroll) {
    774         if (date.before(mMinDate) || date.after(mMaxDate)) {
    775             throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
    776                     + " and " + mMaxDate.getTime());
    777         }
    778         // Find the first and last entirely visible weeks
    779         int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
    780         View firstChild = mListView.getChildAt(0);
    781         if (firstChild != null && firstChild.getTop() < 0) {
    782             firstFullyVisiblePosition++;
    783         }
    784         int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
    785         if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
    786             lastFullyVisiblePosition--;
    787         }
    788         if (setSelected) {
    789             mAdapter.setSelectedDay(date);
    790         }
    791         // Get the week we're going to
    792         int position = getWeeksSinceMinDate(date);
    793 
    794         // Check if the selected day is now outside of our visible range
    795         // and if so scroll to the month that contains it
    796         if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
    797                 || forceScroll) {
    798             mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
    799             mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
    800 
    801             setMonthDisplayed(mFirstDayOfMonth);
    802 
    803             // the earliest time we can scroll to is the min date
    804             if (mFirstDayOfMonth.before(mMinDate)) {
    805                 position = 0;
    806             } else {
    807                 position = getWeeksSinceMinDate(mFirstDayOfMonth);
    808             }
    809 
    810             mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING;
    811             if (animate) {
    812                 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
    813                         GOTO_SCROLL_DURATION);
    814             } else {
    815                 mListView.setSelectionFromTop(position, mListScrollTopOffset);
    816                 // Perform any after scroll operations that are needed
    817                 onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE);
    818             }
    819         } else if (setSelected) {
    820             // Otherwise just set the selection
    821             setMonthDisplayed(date);
    822         }
    823     }
    824 
    825     /**
    826      * Called when a <code>view</code> transitions to a new <code>scrollState
    827      * </code>.
    828      */
    829     private void onScrollStateChanged(AbsListView view, int scrollState) {
    830         mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
    831     }
    832 
    833     /**
    834      * Updates the title and selected month if the <code>view</code> has moved to a new
    835      * month.
    836      */
    837     private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    838                           int totalItemCount) {
    839         WeekView child = (WeekView) view.getChildAt(0);
    840         if (child == null) {
    841             return;
    842         }
    843 
    844         // Figure out where we are
    845         long currScroll =
    846                 view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
    847 
    848         // If we have moved since our last call update the direction
    849         if (currScroll < mPreviousScrollPosition) {
    850             mIsScrollingUp = true;
    851         } else if (currScroll > mPreviousScrollPosition) {
    852             mIsScrollingUp = false;
    853         } else {
    854             return;
    855         }
    856 
    857         // Use some hysteresis for checking which month to highlight. This
    858         // causes the month to transition when two full weeks of a month are
    859         // visible when scrolling up, and when the first day in a month reaches
    860         // the top of the screen when scrolling down.
    861         int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
    862         if (mIsScrollingUp) {
    863             child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
    864         } else if (offset != 0) {
    865             child = (WeekView) view.getChildAt(offset);
    866         }
    867 
    868         if (child != null) {
    869             // Find out which month we're moving into
    870             int month;
    871             if (mIsScrollingUp) {
    872                 month = child.getMonthOfFirstWeekDay();
    873             } else {
    874                 month = child.getMonthOfLastWeekDay();
    875             }
    876 
    877             // And how it relates to our current highlighted month
    878             int monthDiff;
    879             if (mCurrentMonthDisplayed == 11 && month == 0) {
    880                 monthDiff = 1;
    881             } else if (mCurrentMonthDisplayed == 0 && month == 11) {
    882                 monthDiff = -1;
    883             } else {
    884                 monthDiff = month - mCurrentMonthDisplayed;
    885             }
    886 
    887             // Only switch months if we're scrolling away from the currently
    888             // selected month
    889             if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
    890                 Calendar firstDay = child.getFirstDay();
    891                 if (mIsScrollingUp) {
    892                     firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
    893                 } else {
    894                     firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
    895                 }
    896                 setMonthDisplayed(firstDay);
    897             }
    898         }
    899         mPreviousScrollPosition = currScroll;
    900         mPreviousScrollState = mCurrentScrollState;
    901     }
    902 
    903     /**
    904      * Sets the month displayed at the top of this view based on time. Override
    905      * to add custom events when the title is changed.
    906      *
    907      * @param calendar A day in the new focus month.
    908      */
    909     private void setMonthDisplayed(Calendar calendar) {
    910         mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
    911         mAdapter.setFocusMonth(mCurrentMonthDisplayed);
    912         final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
    913                 | DateUtils.FORMAT_SHOW_YEAR;
    914         final long millis = calendar.getTimeInMillis();
    915         String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
    916         mMonthName.setText(newMonthName);
    917         mMonthName.invalidate();
    918     }
    919 
    920     /**
    921      * @return Returns the number of weeks between the current <code>date</code>
    922      *         and the <code>mMinDate</code>.
    923      */
    924     private int getWeeksSinceMinDate(Calendar date) {
    925         if (date.before(mMinDate)) {
    926             throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
    927                     + " does not precede toDate: " + date.getTime());
    928         }
    929         long endTimeMillis = date.getTimeInMillis()
    930                 + date.getTimeZone().getOffset(date.getTimeInMillis());
    931         long startTimeMillis = mMinDate.getTimeInMillis()
    932                 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
    933         long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
    934                 * MILLIS_IN_DAY;
    935         return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
    936     }
    937 
    938     /**
    939      * Command responsible for acting upon scroll state changes.
    940      */
    941     private class ScrollStateRunnable implements Runnable {
    942         private AbsListView mView;
    943 
    944         private int mNewState;
    945 
    946         /**
    947          * Sets up the runnable with a short delay in case the scroll state
    948          * immediately changes again.
    949          *
    950          * @param view The list view that changed state
    951          * @param scrollState The new state it changed to
    952          */
    953         public void doScrollStateChange(AbsListView view, int scrollState) {
    954             mView = view;
    955             mNewState = scrollState;
    956             mDelegator.removeCallbacks(this);
    957             mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
    958         }
    959 
    960         public void run() {
    961             mCurrentScrollState = mNewState;
    962             // Fix the position after a scroll or a fling ends
    963             if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
    964                     && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
    965                 View child = mView.getChildAt(0);
    966                 if (child == null) {
    967                     // The view is no longer visible, just return
    968                     return;
    969                 }
    970                 int dist = child.getBottom() - mListScrollTopOffset;
    971                 if (dist > mListScrollTopOffset) {
    972                     if (mIsScrollingUp) {
    973                         mView.smoothScrollBy(dist - child.getHeight(),
    974                                 ADJUSTMENT_SCROLL_DURATION);
    975                     } else {
    976                         mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
    977                     }
    978                 }
    979             }
    980             mPreviousScrollState = mNewState;
    981         }
    982     }
    983 
    984     /**
    985      * <p>
    986      * This is a specialized adapter for creating a list of weeks with
    987      * selectable days. It can be configured to display the week number, start
    988      * the week on a given day, show a reduced number of days, or display an
    989      * arbitrary number of weeks at a time.
    990      * </p>
    991      */
    992     private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener {
    993 
    994         private int mSelectedWeek;
    995 
    996         private GestureDetector mGestureDetector;
    997 
    998         private int mFocusedMonth;
    999 
   1000         private final Calendar mSelectedDate = Calendar.getInstance();
   1001 
   1002         private int mTotalWeekCount;
   1003 
   1004         public WeeksAdapter(Context context) {
   1005             mContext = context;
   1006             mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener());
   1007             init();
   1008         }
   1009 
   1010         /**
   1011          * Set up the gesture detector and selected time
   1012          */
   1013         private void init() {
   1014             mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
   1015             mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
   1016             if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
   1017                     || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
   1018                 mTotalWeekCount++;
   1019             }
   1020             notifyDataSetChanged();
   1021         }
   1022 
   1023         /**
   1024          * Updates the selected day and related parameters.
   1025          *
   1026          * @param selectedDay The time to highlight
   1027          */
   1028         public void setSelectedDay(Calendar selectedDay) {
   1029             if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
   1030                     && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
   1031                 return;
   1032             }
   1033             mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
   1034             mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
   1035             mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
   1036             notifyDataSetChanged();
   1037         }
   1038 
   1039         /**
   1040          * @return The selected day of month.
   1041          */
   1042         public Calendar getSelectedDay() {
   1043             return mSelectedDate;
   1044         }
   1045 
   1046         @Override
   1047         public int getCount() {
   1048             return mTotalWeekCount;
   1049         }
   1050 
   1051         @Override
   1052         public Object getItem(int position) {
   1053             return null;
   1054         }
   1055 
   1056         @Override
   1057         public long getItemId(int position) {
   1058             return position;
   1059         }
   1060 
   1061         @Override
   1062         public View getView(int position, View convertView, ViewGroup parent) {
   1063             WeekView weekView = null;
   1064             if (convertView != null) {
   1065                 weekView = (WeekView) convertView;
   1066             } else {
   1067                 weekView = new WeekView(mContext);
   1068                 AbsListView.LayoutParams params =
   1069                         new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
   1070                                 FrameLayout.LayoutParams.WRAP_CONTENT);
   1071                 weekView.setLayoutParams(params);
   1072                 weekView.setClickable(true);
   1073                 weekView.setOnTouchListener(this);
   1074             }
   1075 
   1076             int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
   1077                     Calendar.DAY_OF_WEEK) : -1;
   1078             weekView.init(position, selectedWeekDay, mFocusedMonth);
   1079 
   1080             return weekView;
   1081         }
   1082 
   1083         /**
   1084          * Changes which month is in focus and updates the view.
   1085          *
   1086          * @param month The month to show as in focus [0-11]
   1087          */
   1088         public void setFocusMonth(int month) {
   1089             if (mFocusedMonth == month) {
   1090                 return;
   1091             }
   1092             mFocusedMonth = month;
   1093             notifyDataSetChanged();
   1094         }
   1095 
   1096         @Override
   1097         public boolean onTouch(View v, MotionEvent event) {
   1098             if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
   1099                 WeekView weekView = (WeekView) v;
   1100                 // if we cannot find a day for the given location we are done
   1101                 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
   1102                     return true;
   1103                 }
   1104                 // it is possible that the touched day is outside the valid range
   1105                 // we draw whole weeks but range end can fall not on the week end
   1106                 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
   1107                     return true;
   1108                 }
   1109                 onDateTapped(mTempDate);
   1110                 return true;
   1111             }
   1112             return false;
   1113         }
   1114 
   1115         /**
   1116          * Maintains the same hour/min/sec but moves the day to the tapped day.
   1117          *
   1118          * @param day The day that was tapped
   1119          */
   1120         private void onDateTapped(Calendar day) {
   1121             setSelectedDay(day);
   1122             setMonthDisplayed(day);
   1123         }
   1124 
   1125         /**
   1126          * This is here so we can identify single tap events and set the
   1127          * selected day correctly
   1128          */
   1129         class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
   1130             @Override
   1131             public boolean onSingleTapUp(MotionEvent e) {
   1132                 return true;
   1133             }
   1134         }
   1135     }
   1136 
   1137     /**
   1138      * <p>
   1139      * This is a dynamic view for drawing a single week. It can be configured to
   1140      * display the week number, start the week on a given day, or show a reduced
   1141      * number of days. It is intended for use as a single view within a
   1142      * ListView. See {@link WeeksAdapter} for usage.
   1143      * </p>
   1144      */
   1145     private class WeekView extends View {
   1146 
   1147         private final Rect mTempRect = new Rect();
   1148 
   1149         private final Paint mDrawPaint = new Paint();
   1150 
   1151         private final Paint mMonthNumDrawPaint = new Paint();
   1152 
   1153         // Cache the number strings so we don't have to recompute them each time
   1154         private String[] mDayNumbers;
   1155 
   1156         // Quick lookup for checking which days are in the focus month
   1157         private boolean[] mFocusDay;
   1158 
   1159         // Whether this view has a focused day.
   1160         private boolean mHasFocusedDay;
   1161 
   1162         // Whether this view has only focused days.
   1163         private boolean mHasUnfocusedDay;
   1164 
   1165         // The first day displayed by this item
   1166         private Calendar mFirstDay;
   1167 
   1168         // The month of the first day in this week
   1169         private int mMonthOfFirstWeekDay = -1;
   1170 
   1171         // The month of the last day in this week
   1172         private int mLastWeekDayMonth = -1;
   1173 
   1174         // The position of this week, equivalent to weeks since the week of Jan
   1175         // 1st, 1900
   1176         private int mWeek = -1;
   1177 
   1178         // Quick reference to the width of this view, matches parent
   1179         private int mWidth;
   1180 
   1181         // The height this view should draw at in pixels, set by height param
   1182         private int mHeight;
   1183 
   1184         // If this view contains the selected day
   1185         private boolean mHasSelectedDay = false;
   1186 
   1187         // Which day is selected [0-6] or -1 if no day is selected
   1188         private int mSelectedDay = -1;
   1189 
   1190         // The number of days + a spot for week number if it is displayed
   1191         private int mNumCells;
   1192 
   1193         // The left edge of the selected day
   1194         private int mSelectedLeft = -1;
   1195 
   1196         // The right edge of the selected day
   1197         private int mSelectedRight = -1;
   1198 
   1199         public WeekView(Context context) {
   1200             super(context);
   1201 
   1202             // Sets up any standard paints that will be used
   1203             initilaizePaints();
   1204         }
   1205 
   1206         /**
   1207          * Initializes this week view.
   1208          *
   1209          * @param weekNumber The number of the week this view represents. The
   1210          *            week number is a zero based index of the weeks since
   1211          *            {@link android.widget.CalendarView#getMinDate()}.
   1212          * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
   1213          *            selected day.
   1214          * @param focusedMonth The month that is currently in focus i.e.
   1215          *            highlighted.
   1216          */
   1217         public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
   1218             mSelectedDay = selectedWeekDay;
   1219             mHasSelectedDay = mSelectedDay != -1;
   1220             mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
   1221             mWeek = weekNumber;
   1222             mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
   1223 
   1224             mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
   1225             mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
   1226 
   1227             // Allocate space for caching the day numbers and focus values
   1228             mDayNumbers = new String[mNumCells];
   1229             mFocusDay = new boolean[mNumCells];
   1230 
   1231             // If we're showing the week number calculate it based on Monday
   1232             int i = 0;
   1233             if (mShowWeekNumber) {
   1234                 mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
   1235                         mTempDate.get(Calendar.WEEK_OF_YEAR));
   1236                 i++;
   1237             }
   1238 
   1239             // Now adjust our starting day based on the start day of the week
   1240             int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
   1241             mTempDate.add(Calendar.DAY_OF_MONTH, diff);
   1242 
   1243             mFirstDay = (Calendar) mTempDate.clone();
   1244             mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
   1245 
   1246             mHasUnfocusedDay = true;
   1247             for (; i < mNumCells; i++) {
   1248                 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
   1249                 mFocusDay[i] = isFocusedDay;
   1250                 mHasFocusedDay |= isFocusedDay;
   1251                 mHasUnfocusedDay &= !isFocusedDay;
   1252                 // do not draw dates outside the valid range to avoid user confusion
   1253                 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
   1254                     mDayNumbers[i] = "";
   1255                 } else {
   1256                     mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
   1257                             mTempDate.get(Calendar.DAY_OF_MONTH));
   1258                 }
   1259                 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
   1260             }
   1261             // We do one extra add at the end of the loop, if that pushed us to
   1262             // new month undo it
   1263             if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
   1264                 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
   1265             }
   1266             mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
   1267 
   1268             updateSelectionPositions();
   1269         }
   1270 
   1271         /**
   1272          * Initialize the paint instances.
   1273          */
   1274         private void initilaizePaints() {
   1275             mDrawPaint.setFakeBoldText(false);
   1276             mDrawPaint.setAntiAlias(true);
   1277             mDrawPaint.setStyle(Paint.Style.FILL);
   1278 
   1279             mMonthNumDrawPaint.setFakeBoldText(true);
   1280             mMonthNumDrawPaint.setAntiAlias(true);
   1281             mMonthNumDrawPaint.setStyle(Paint.Style.FILL);
   1282             mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER);
   1283             mMonthNumDrawPaint.setTextSize(mDateTextSize);
   1284         }
   1285 
   1286         /**
   1287          * Returns the month of the first day in this week.
   1288          *
   1289          * @return The month the first day of this view is in.
   1290          */
   1291         public int getMonthOfFirstWeekDay() {
   1292             return mMonthOfFirstWeekDay;
   1293         }
   1294 
   1295         /**
   1296          * Returns the month of the last day in this week
   1297          *
   1298          * @return The month the last day of this view is in
   1299          */
   1300         public int getMonthOfLastWeekDay() {
   1301             return mLastWeekDayMonth;
   1302         }
   1303 
   1304         /**
   1305          * Returns the first day in this view.
   1306          *
   1307          * @return The first day in the view.
   1308          */
   1309         public Calendar getFirstDay() {
   1310             return mFirstDay;
   1311         }
   1312 
   1313         /**
   1314          * Calculates the day that the given x position is in, accounting for
   1315          * week number.
   1316          *
   1317          * @param x The x position of the touch event.
   1318          * @return True if a day was found for the given location.
   1319          */
   1320         public boolean getDayFromLocation(float x, Calendar outCalendar) {
   1321             final boolean isLayoutRtl = isLayoutRtl();
   1322 
   1323             int start;
   1324             int end;
   1325 
   1326             if (isLayoutRtl) {
   1327                 start = 0;
   1328                 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
   1329             } else {
   1330                 start = mShowWeekNumber ? mWidth / mNumCells : 0;
   1331                 end = mWidth;
   1332             }
   1333 
   1334             if (x < start || x > end) {
   1335                 outCalendar.clear();
   1336                 return false;
   1337             }
   1338 
   1339             // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
   1340             int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
   1341 
   1342             if (isLayoutRtl) {
   1343                 dayPosition = mDaysPerWeek - 1 - dayPosition;
   1344             }
   1345 
   1346             outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
   1347             outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
   1348 
   1349             return true;
   1350         }
   1351 
   1352         @Override
   1353         protected void onDraw(Canvas canvas) {
   1354             drawBackground(canvas);
   1355             drawWeekNumbersAndDates(canvas);
   1356             drawWeekSeparators(canvas);
   1357             drawSelectedDateVerticalBars(canvas);
   1358         }
   1359 
   1360         /**
   1361          * This draws the selection highlight if a day is selected in this week.
   1362          *
   1363          * @param canvas The canvas to draw on
   1364          */
   1365         private void drawBackground(Canvas canvas) {
   1366             if (!mHasSelectedDay) {
   1367                 return;
   1368             }
   1369             mDrawPaint.setColor(mSelectedWeekBackgroundColor);
   1370 
   1371             mTempRect.top = mWeekSeperatorLineWidth;
   1372             mTempRect.bottom = mHeight;
   1373 
   1374             final boolean isLayoutRtl = isLayoutRtl();
   1375 
   1376             if (isLayoutRtl) {
   1377                 mTempRect.left = 0;
   1378                 mTempRect.right = mSelectedLeft - 2;
   1379             } else {
   1380                 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
   1381                 mTempRect.right = mSelectedLeft - 2;
   1382             }
   1383             canvas.drawRect(mTempRect, mDrawPaint);
   1384 
   1385             if (isLayoutRtl) {
   1386                 mTempRect.left = mSelectedRight + 3;
   1387                 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
   1388             } else {
   1389                 mTempRect.left = mSelectedRight + 3;
   1390                 mTempRect.right = mWidth;
   1391             }
   1392             canvas.drawRect(mTempRect, mDrawPaint);
   1393         }
   1394 
   1395         /**
   1396          * Draws the week and month day numbers for this week.
   1397          *
   1398          * @param canvas The canvas to draw on
   1399          */
   1400         private void drawWeekNumbersAndDates(Canvas canvas) {
   1401             final float textHeight = mDrawPaint.getTextSize();
   1402             final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
   1403             final int nDays = mNumCells;
   1404             final int divisor = 2 * nDays;
   1405 
   1406             mDrawPaint.setTextAlign(Paint.Align.CENTER);
   1407             mDrawPaint.setTextSize(mDateTextSize);
   1408 
   1409             int i = 0;
   1410 
   1411             if (isLayoutRtl()) {
   1412                 for (; i < nDays - 1; i++) {
   1413                     mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
   1414                             : mUnfocusedMonthDateColor);
   1415                     int x = (2 * i + 1) * mWidth / divisor;
   1416                     canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
   1417                 }
   1418                 if (mShowWeekNumber) {
   1419                     mDrawPaint.setColor(mWeekNumberColor);
   1420                     int x = mWidth - mWidth / divisor;
   1421                     canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
   1422                 }
   1423             } else {
   1424                 if (mShowWeekNumber) {
   1425                     mDrawPaint.setColor(mWeekNumberColor);
   1426                     int x = mWidth / divisor;
   1427                     canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
   1428                     i++;
   1429                 }
   1430                 for (; i < nDays; i++) {
   1431                     mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
   1432                             : mUnfocusedMonthDateColor);
   1433                     int x = (2 * i + 1) * mWidth / divisor;
   1434                     canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
   1435                 }
   1436             }
   1437         }
   1438 
   1439         /**
   1440          * Draws a horizontal line for separating the weeks.
   1441          *
   1442          * @param canvas The canvas to draw on.
   1443          */
   1444         private void drawWeekSeparators(Canvas canvas) {
   1445             // If it is the topmost fully visible child do not draw separator line
   1446             int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
   1447             if (mListView.getChildAt(0).getTop() < 0) {
   1448                 firstFullyVisiblePosition++;
   1449             }
   1450             if (firstFullyVisiblePosition == mWeek) {
   1451                 return;
   1452             }
   1453             mDrawPaint.setColor(mWeekSeparatorLineColor);
   1454             mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
   1455             float startX;
   1456             float stopX;
   1457             if (isLayoutRtl()) {
   1458                 startX = 0;
   1459                 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
   1460             } else {
   1461                 startX = mShowWeekNumber ? mWidth / mNumCells : 0;
   1462                 stopX = mWidth;
   1463             }
   1464             canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
   1465         }
   1466 
   1467         /**
   1468          * Draws the selected date bars if this week has a selected day.
   1469          *
   1470          * @param canvas The canvas to draw on
   1471          */
   1472         private void drawSelectedDateVerticalBars(Canvas canvas) {
   1473             if (!mHasSelectedDay) {
   1474                 return;
   1475             }
   1476             mSelectedDateVerticalBar.setBounds(
   1477                     mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
   1478                     mWeekSeperatorLineWidth,
   1479                     mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
   1480                     mHeight);
   1481             mSelectedDateVerticalBar.draw(canvas);
   1482             mSelectedDateVerticalBar.setBounds(
   1483                     mSelectedRight - mSelectedDateVerticalBarWidth / 2,
   1484                     mWeekSeperatorLineWidth,
   1485                     mSelectedRight + mSelectedDateVerticalBarWidth / 2,
   1486                     mHeight);
   1487             mSelectedDateVerticalBar.draw(canvas);
   1488         }
   1489 
   1490         @Override
   1491         protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1492             mWidth = w;
   1493             updateSelectionPositions();
   1494         }
   1495 
   1496         /**
   1497          * This calculates the positions for the selected day lines.
   1498          */
   1499         private void updateSelectionPositions() {
   1500             if (mHasSelectedDay) {
   1501                 final boolean isLayoutRtl = isLayoutRtl();
   1502                 int selectedPosition = mSelectedDay - mFirstDayOfWeek;
   1503                 if (selectedPosition < 0) {
   1504                     selectedPosition += 7;
   1505                 }
   1506                 if (mShowWeekNumber && !isLayoutRtl) {
   1507                     selectedPosition++;
   1508                 }
   1509                 if (isLayoutRtl) {
   1510                     mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
   1511 
   1512                 } else {
   1513                     mSelectedLeft = selectedPosition * mWidth / mNumCells;
   1514                 }
   1515                 mSelectedRight = mSelectedLeft + mWidth / mNumCells;
   1516             }
   1517         }
   1518 
   1519         @Override
   1520         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1521             mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
   1522                     .getPaddingBottom()) / mShownWeekCount;
   1523             setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
   1524         }
   1525     }
   1526 
   1527 }
   1528