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