/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.app.Service;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.icu.util.Calendar;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.android.internal.R;

import libcore.icu.LocaleData;

import java.util.Locale;

A delegate implementing the legacy CalendarView
/** * A delegate implementing the legacy CalendarView */
class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate {
Default value whether to show week number.
/** * Default value whether to show week number. */
private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
The number of milliseconds in a day.e
/** * The number of milliseconds in a day.e */
private static final long MILLIS_IN_DAY = 86400000L;
The number of day in a week.
/** * The number of day in a week. */
private static final int DAYS_PER_WEEK = 7;
The number of milliseconds in a week.
/** * The number of milliseconds in a week. */
private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
Affects when the month selection will change while scrolling upe
/** * Affects when the month selection will change while scrolling upe */
private static final int SCROLL_HYST_WEEKS = 2;
How long the GoTo fling animation should last.
/** * How long the GoTo fling animation should last. */
private static final int GOTO_SCROLL_DURATION = 1000;
The duration of the adjustment upon a user scroll in milliseconds.
/** * The duration of the adjustment upon a user scroll in milliseconds. */
private static final int ADJUSTMENT_SCROLL_DURATION = 500;
How long to wait after receiving an onScrollStateChanged notification before acting on it.
/** * How long to wait after receiving an onScrollStateChanged notification * before acting on it. */
private static final int SCROLL_CHANGE_DELAY = 40; private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; private static final int DEFAULT_DATE_TEXT_SIZE = 14; private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; private static final int UNSCALED_BOTTOM_BUFFER = 20; private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; private final int mWeekSeparatorLineWidth; private int mDateTextSize; private Drawable mSelectedDateVerticalBar; private final int mSelectedDateVerticalBarWidth; private int mSelectedWeekBackgroundColor; private int mFocusedMonthDateColor; private int mUnfocusedMonthDateColor; private int mWeekSeparatorLineColor; private int mWeekNumberColor; private int mWeekDayTextAppearanceResId; private int mDateTextAppearanceResId;
The top offset of the weeks list.
/** * The top offset of the weeks list. */
private int mListScrollTopOffset = 2;
The visible height of a week view.
/** * The visible height of a week view. */
private int mWeekMinVisibleHeight = 12;
The visible height of a week view.
/** * The visible height of a week view. */
private int mBottomBuffer = 20;
The number of shown weeks.
/** * The number of shown weeks. */
private int mShownWeekCount;
Flag whether to show the week number.
/** * Flag whether to show the week number. */
private boolean mShowWeekNumber;
The number of day per week to be shown.
/** * The number of day per week to be shown. */
private int mDaysPerWeek = 7;
The friction of the week list while flinging.
/** * The friction of the week list while flinging. */
private float mFriction = .05f;
Scale for adjusting velocity of the week list while flinging.
/** * Scale for adjusting velocity of the week list while flinging. */
private float mVelocityScale = 0.333f;
The adapter for the weeks list.
/** * The adapter for the weeks list. */
private WeeksAdapter mAdapter;
The weeks list.
/** * The weeks list. */
private ListView mListView;
The name of the month to display.
/** * The name of the month to display. */
private TextView mMonthName;
The header with week day names.
/** * The header with week day names. */
private ViewGroup mDayNamesHeader;
Cached abbreviations for day of week names.
/** * Cached abbreviations for day of week names. */
private String[] mDayNamesShort;
Cached full-length day of week names.
/** * Cached full-length day of week names. */
private String[] mDayNamesLong;
The first day of the week.
/** * The first day of the week. */
private int mFirstDayOfWeek;
Which month should be displayed/highlighted [0-11].
/** * Which month should be displayed/highlighted [0-11]. */
private int mCurrentMonthDisplayed = -1;
Used for tracking during a scroll.
/** * Used for tracking during a scroll. */
private long mPreviousScrollPosition;
Used for tracking which direction the view is scrolling.
/** * Used for tracking which direction the view is scrolling. */
private boolean mIsScrollingUp = false;
The previous scroll state of the weeks ListView.
/** * The previous scroll state of the weeks ListView. */
private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
The current scroll state of the weeks ListView.
/** * The current scroll state of the weeks ListView. */
private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
Listener for changes in the selected day.
/** * Listener for changes in the selected day. */
private CalendarView.OnDateChangeListener mOnDateChangeListener;
Command for adjusting the position after a scroll/fling.
/** * Command for adjusting the position after a scroll/fling. */
private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
Temporary instance to avoid multiple instantiations.
/** * Temporary instance to avoid multiple instantiations. */
private Calendar mTempDate;
The first day of the focused month.
/** * The first day of the focused month. */
private Calendar mFirstDayOfMonth;
The start date of the range supported by this picker.
/** * The start date of the range supported by this picker. */
private Calendar mMinDate;
The end date of the range supported by this picker.
/** * The end date of the range supported by this picker. */
private Calendar mMaxDate; CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(delegator, context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CalendarView, defStyleAttr, defStyleRes); mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber, DEFAULT_SHOW_WEEK_NUMBER); mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, LocaleData.get(Locale.getDefault()).firstDayOfWeek); final String minDate = a.getString(R.styleable.CalendarView_minDate); if (!CalendarView.parseDate(minDate, mMinDate)) { CalendarView.parseDate(DEFAULT_MIN_DATE, mMinDate); } final String maxDate = a.getString(R.styleable.CalendarView_maxDate); if (!CalendarView.parseDate(maxDate, mMaxDate)) { CalendarView.parseDate(DEFAULT_MAX_DATE, mMaxDate); } if (mMaxDate.before(mMinDate)) { throw new IllegalArgumentException("Max date cannot be before min date."); } mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount, DEFAULT_SHOWN_WEEK_COUNT); mSelectedWeekBackgroundColor = a.getColor( R.styleable.CalendarView_selectedWeekBackgroundColor, 0); mFocusedMonthDateColor = a.getColor( R.styleable.CalendarView_focusedMonthDateColor, 0); mUnfocusedMonthDateColor = a.getColor( R.styleable.CalendarView_unfocusedMonthDateColor, 0); mWeekSeparatorLineColor = a.getColor( R.styleable.CalendarView_weekSeparatorLineColor, 0); mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0); mSelectedDateVerticalBar = a.getDrawable( R.styleable.CalendarView_selectedDateVerticalBar); mDateTextAppearanceResId = a.getResourceId( R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); updateDateTextSize(); mWeekDayTextAppearanceResId = a.getResourceId( R.styleable.CalendarView_weekDayTextAppearance, DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); a.recycle(); DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_BOTTOM_BUFFER, displayMetrics); mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); mWeekSeparatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); LayoutInflater layoutInflater = (LayoutInflater) mContext .getSystemService(Service.LAYOUT_INFLATER_SERVICE); View content = layoutInflater.inflate(R.layout.calendar_view, null, false); mDelegator.addView(content); mListView = mDelegator.findViewById(R.id.list); mDayNamesHeader = content.findViewById(R.id.day_names); mMonthName = content.findViewById(R.id.month_name); setUpHeader(); setUpListView(); setUpAdapter(); // go to today or whichever is close to today min or max date mTempDate.setTimeInMillis(System.currentTimeMillis()); if (mTempDate.before(mMinDate)) { goTo(mMinDate, false, true, true); } else if (mMaxDate.before(mTempDate)) { goTo(mMaxDate, false, true, true); } else { goTo(mTempDate, false, true, true); } mDelegator.invalidate(); } @Override public void setShownWeekCount(int count) { if (mShownWeekCount != count) { mShownWeekCount = count; mDelegator.invalidate(); } } @Override public int getShownWeekCount() { return mShownWeekCount; } @Override public void setSelectedWeekBackgroundColor(int color) { if (mSelectedWeekBackgroundColor != color) { mSelectedWeekBackgroundColor = color; final int childCount = mListView.getChildCount(); for (int i = 0; i < childCount; i++) { WeekView weekView = (WeekView) mListView.getChildAt(i); if (weekView.mHasSelectedDay) { weekView.invalidate(); } } } } @Override public int getSelectedWeekBackgroundColor() { return mSelectedWeekBackgroundColor; } @Override public void setFocusedMonthDateColor(int color) { if (mFocusedMonthDateColor != color) { mFocusedMonthDateColor = color; final int childCount = mListView.getChildCount(); for (int i = 0; i < childCount; i++) { WeekView weekView = (WeekView) mListView.getChildAt(i); if (weekView.mHasFocusedDay) { weekView.invalidate(); } } } } @Override public int getFocusedMonthDateColor() { return mFocusedMonthDateColor; } @Override public void setUnfocusedMonthDateColor(int color) { if (mUnfocusedMonthDateColor != color) { mUnfocusedMonthDateColor = color; final int childCount = mListView.getChildCount(); for (int i = 0; i < childCount; i++) { WeekView weekView = (WeekView) mListView.getChildAt(i); if (weekView.mHasUnfocusedDay) { weekView.invalidate(); } } } } @Override public int getUnfocusedMonthDateColor() { return mUnfocusedMonthDateColor; } @Override public void setWeekNumberColor(int color) { if (mWeekNumberColor != color) { mWeekNumberColor = color; if (mShowWeekNumber) { invalidateAllWeekViews(); } } } @Override public int getWeekNumberColor() { return mWeekNumberColor; } @Override public void setWeekSeparatorLineColor(int color) { if (mWeekSeparatorLineColor != color) { mWeekSeparatorLineColor = color; invalidateAllWeekViews(); } } @Override public int getWeekSeparatorLineColor() { return mWeekSeparatorLineColor; } @Override public void setSelectedDateVerticalBar(int resourceId) { Drawable drawable = mDelegator.getContext().getDrawable(resourceId); setSelectedDateVerticalBar(drawable); } @Override public void setSelectedDateVerticalBar(Drawable drawable) { if (mSelectedDateVerticalBar != drawable) { mSelectedDateVerticalBar = drawable; final int childCount = mListView.getChildCount(); for (int i = 0; i < childCount; i++) { WeekView weekView = (WeekView) mListView.getChildAt(i); if (weekView.mHasSelectedDay) { weekView.invalidate(); } } } } @Override public Drawable getSelectedDateVerticalBar() { return mSelectedDateVerticalBar; } @Override public void setWeekDayTextAppearance(int resourceId) { if (mWeekDayTextAppearanceResId != resourceId) { mWeekDayTextAppearanceResId = resourceId; setUpHeader(); } } @Override public int getWeekDayTextAppearance() { return mWeekDayTextAppearanceResId; } @Override public void setDateTextAppearance(int resourceId) { if (mDateTextAppearanceResId != resourceId) { mDateTextAppearanceResId = resourceId; updateDateTextSize(); invalidateAllWeekViews(); } } @Override public int getDateTextAppearance() { return mDateTextAppearanceResId; } @Override public void setMinDate(long minDate) { mTempDate.setTimeInMillis(minDate); if (isSameDate(mTempDate, mMinDate)) { return; } mMinDate.setTimeInMillis(minDate); // make sure the current date is not earlier than // the new min date since the latter is used for // calculating the indices in the adapter thus // avoiding out of bounds error Calendar date = mAdapter.mSelectedDate; if (date.before(mMinDate)) { mAdapter.setSelectedDay(mMinDate); } // reinitialize the adapter since its range depends on min date mAdapter.init(); if (date.before(mMinDate)) { setDate(mTempDate.getTimeInMillis()); } else { // we go to the current date to force the ListView to query its // adapter for the shown views since we have changed the adapter // range and the base from which the later calculates item indices // note that calling setDate will not work since the date is the same goTo(date, false, true, false); } } @Override public long getMinDate() { return mMinDate.getTimeInMillis(); } @Override public void setMaxDate(long maxDate) { mTempDate.setTimeInMillis(maxDate); if (isSameDate(mTempDate, mMaxDate)) { return; } mMaxDate.setTimeInMillis(maxDate); // reinitialize the adapter since its range depends on max date mAdapter.init(); Calendar date = mAdapter.mSelectedDate; if (date.after(mMaxDate)) { setDate(mMaxDate.getTimeInMillis()); } else { // we go to the current date to force the ListView to query its // adapter for the shown views since we have changed the adapter // range and the base from which the later calculates item indices // note that calling setDate will not work since the date is the same goTo(date, false, true, false); } } @Override public long getMaxDate() { return mMaxDate.getTimeInMillis(); } @Override public void setShowWeekNumber(boolean showWeekNumber) { if (mShowWeekNumber == showWeekNumber) { return; } mShowWeekNumber = showWeekNumber; mAdapter.notifyDataSetChanged(); setUpHeader(); } @Override public boolean getShowWeekNumber() { return mShowWeekNumber; } @Override public void setFirstDayOfWeek(int firstDayOfWeek) { if (mFirstDayOfWeek == firstDayOfWeek) { return; } mFirstDayOfWeek = firstDayOfWeek; mAdapter.init(); mAdapter.notifyDataSetChanged(); setUpHeader(); } @Override public int getFirstDayOfWeek() { return mFirstDayOfWeek; } @Override public void setDate(long date) { setDate(date, false, false); } @Override public void setDate(long date, boolean animate, boolean center) { mTempDate.setTimeInMillis(date); if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { return; } goTo(mTempDate, animate, true, center); } @Override public long getDate() { return mAdapter.mSelectedDate.getTimeInMillis(); } @Override public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) { mOnDateChangeListener = listener; } @Override public boolean getBoundsForDate(long date, Rect outBounds) { Calendar calendarDate = Calendar.getInstance(); calendarDate.setTimeInMillis(date); int listViewEntryCount = mListView.getCount(); for (int i = 0; i < listViewEntryCount; i++) { WeekView currWeekView = (WeekView) mListView.getChildAt(i); if (currWeekView.getBoundsForDate(calendarDate, outBounds)) { // Found the date in this week. Now need to offset vertically to return correct // bounds in the coordinate system of the entire layout final int[] weekViewPositionOnScreen = new int[2]; final int[] delegatorPositionOnScreen = new int[2]; currWeekView.getLocationOnScreen(weekViewPositionOnScreen); mDelegator.getLocationOnScreen(delegatorPositionOnScreen); final int extraVerticalOffset = weekViewPositionOnScreen[1] - delegatorPositionOnScreen[1]; outBounds.top += extraVerticalOffset; outBounds.bottom += extraVerticalOffset; return true; } } return false; } @Override public void onConfigurationChanged(Configuration newConfig) { setCurrentLocale(newConfig.locale); }
Sets the current locale.
Params:
  • locale – The current locale.
/** * Sets the current locale. * * @param locale The current locale. */
@Override protected void setCurrentLocale(Locale locale) { super.setCurrentLocale(locale); mTempDate = getCalendarForLocale(mTempDate, locale); mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); mMinDate = getCalendarForLocale(mMinDate, locale); mMaxDate = getCalendarForLocale(mMaxDate, locale); } private void updateDateTextSize() { TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( mDateTextAppearanceResId, R.styleable.TextAppearance); mDateTextSize = dateTextAppearance.getDimensionPixelSize( R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); dateTextAppearance.recycle(); }
Invalidates all week views.
/** * Invalidates all week views. */
private void invalidateAllWeekViews() { final int childCount = mListView.getChildCount(); for (int i = 0; i < childCount; i++) { View view = mListView.getChildAt(i); view.invalidate(); } }
Gets a calendar for locale bootstrapped with the value of a given calendar.
Params:
  • oldCalendar – The old calendar.
  • locale – The locale.
/** * Gets a calendar for locale bootstrapped with the value of a given calendar. * * @param oldCalendar The old calendar. * @param locale The locale. */
private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { if (oldCalendar == null) { return Calendar.getInstance(locale); } else { final long currentTimeMillis = oldCalendar.getTimeInMillis(); Calendar newCalendar = Calendar.getInstance(locale); newCalendar.setTimeInMillis(currentTimeMillis); return newCalendar; } }
Returns:True if the firstDate is the same as the secondDate.
/** * @return True if the <code>firstDate</code> is the same as the <code> * secondDate</code>. */
private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); }
Creates a new adapter if necessary and sets up its parameters.
/** * Creates a new adapter if necessary and sets up its parameters. */
private void setUpAdapter() { if (mAdapter == null) { mAdapter = new WeeksAdapter(mContext); mAdapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { if (mOnDateChangeListener != null) { Calendar selectedDay = mAdapter.getSelectedDay(); mOnDateChangeListener.onSelectedDayChange(mDelegator, selectedDay.get(Calendar.YEAR), selectedDay.get(Calendar.MONTH), selectedDay.get(Calendar.DAY_OF_MONTH)); } } }); mListView.setAdapter(mAdapter); } // refresh the view with the new parameters mAdapter.notifyDataSetChanged(); }
Sets up the strings to be used by the header.
/** * Sets up the strings to be used by the header. */
private void setUpHeader() { mDayNamesShort = new String[mDaysPerWeek]; mDayNamesLong = new String[mDaysPerWeek]; for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, DateUtils.LENGTH_SHORTEST); mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, DateUtils.LENGTH_LONG); } TextView label = (TextView) mDayNamesHeader.getChildAt(0); if (mShowWeekNumber) { label.setVisibility(View.VISIBLE); } else { label.setVisibility(View.GONE); } for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { label = (TextView) mDayNamesHeader.getChildAt(i); if (mWeekDayTextAppearanceResId > -1) { label.setTextAppearance(mWeekDayTextAppearanceResId); } if (i < mDaysPerWeek + 1) { label.setText(mDayNamesShort[i - 1]); label.setContentDescription(mDayNamesLong[i - 1]); label.setVisibility(View.VISIBLE); } else { label.setVisibility(View.GONE); } } mDayNamesHeader.invalidate(); }
Sets all the required fields for the list view.
/** * Sets all the required fields for the list view. */
private void setUpListView() { // Configure the listview mListView.setDivider(null); mListView.setItemsCanFocus(true); mListView.setVerticalScrollBarEnabled(false); mListView.setOnScrollListener(new AbsListView.OnScrollListener() { public void onScrollStateChanged(AbsListView view, int scrollState) { CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState); } public void onScroll( AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }); // Make the scrolling behavior nicer mListView.setFriction(mFriction); mListView.setVelocityScale(mVelocityScale); }
This moves to the specified time in the view. If the time is not already in range it will move the list so that the first of the month containing the time is at the top of the view. If the new time is already in view the list will not be scrolled unless forceScroll is true. This time may optionally be highlighted as selected as well.
Params:
  • date – The time to move to.
  • animate – Whether to scroll to the given time or just redraw at the new location.
  • setSelected – Whether to set the given time as selected.
  • forceScroll – Whether to recenter even if the time is already visible.
Throws:
/** * This moves to the specified time in the view. If the time is not already * in range it will move the list so that the first of the month containing * the time is at the top of the view. If the new time is already in view * the list will not be scrolled unless forceScroll is true. This time may * optionally be highlighted as selected as well. * * @param date The time to move to. * @param animate Whether to scroll to the given time or just redraw at the * new location. * @param setSelected Whether to set the given time as selected. * @param forceScroll Whether to recenter even if the time is already * visible. * * @throws IllegalArgumentException if the provided date is before the * range start or after the range end. */
private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) { if (date.before(mMinDate) || date.after(mMaxDate)) { throw new IllegalArgumentException("timeInMillis must be between the values of " + "getMinDate() and getMaxDate()"); } // Find the first and last entirely visible weeks int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); View firstChild = mListView.getChildAt(0); if (firstChild != null && firstChild.getTop() < 0) { firstFullyVisiblePosition++; } int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; if (firstChild != null && firstChild.getTop() > mBottomBuffer) { lastFullyVisiblePosition--; } if (setSelected) { mAdapter.setSelectedDay(date); } // Get the week we're going to int position = getWeeksSinceMinDate(date); // Check if the selected day is now outside of our visible range // and if so scroll to the month that contains it if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition || forceScroll) { mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); setMonthDisplayed(mFirstDayOfMonth); // the earliest time we can scroll to is the min date if (mFirstDayOfMonth.before(mMinDate)) { position = 0; } else { position = getWeeksSinceMinDate(mFirstDayOfMonth); } mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING; if (animate) { mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, GOTO_SCROLL_DURATION); } else { mListView.setSelectionFromTop(position, mListScrollTopOffset); // Perform any after scroll operations that are needed onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE); } } else if (setSelected) { // Otherwise just set the selection setMonthDisplayed(date); } }
Called when a view transitions to a new scrollState .
/** * Called when a <code>view</code> transitions to a new <code>scrollState * </code>. */
private void onScrollStateChanged(AbsListView view, int scrollState) { mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); }
Updates the title and selected month if the view has moved to a new month.
/** * Updates the title and selected month if the <code>view</code> has moved to a new * month. */
private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { WeekView child = (WeekView) view.getChildAt(0); if (child == null) { return; } // Figure out where we are long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); // If we have moved since our last call update the direction if (currScroll < mPreviousScrollPosition) { mIsScrollingUp = true; } else if (currScroll > mPreviousScrollPosition) { mIsScrollingUp = false; } else { return; } // Use some hysteresis for checking which month to highlight. This // causes the month to transition when two full weeks of a month are // visible when scrolling up, and when the first day in a month reaches // the top of the screen when scrolling down. int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; if (mIsScrollingUp) { child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); } else if (offset != 0) { child = (WeekView) view.getChildAt(offset); } if (child != null) { // Find out which month we're moving into int month; if (mIsScrollingUp) { month = child.getMonthOfFirstWeekDay(); } else { month = child.getMonthOfLastWeekDay(); } // And how it relates to our current highlighted month int monthDiff; if (mCurrentMonthDisplayed == 11 && month == 0) { monthDiff = 1; } else if (mCurrentMonthDisplayed == 0 && month == 11) { monthDiff = -1; } else { monthDiff = month - mCurrentMonthDisplayed; } // Only switch months if we're scrolling away from the currently // selected month if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { Calendar firstDay = child.getFirstDay(); if (mIsScrollingUp) { firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); } else { firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); } setMonthDisplayed(firstDay); } } mPreviousScrollPosition = currScroll; mPreviousScrollState = mCurrentScrollState; }
Sets the month displayed at the top of this view based on time. Override to add custom events when the title is changed.
Params:
  • calendar – A day in the new focus month.
/** * Sets the month displayed at the top of this view based on time. Override * to add custom events when the title is changed. * * @param calendar A day in the new focus month. */
private void setMonthDisplayed(Calendar calendar) { mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); mAdapter.setFocusMonth(mCurrentMonthDisplayed); final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY | DateUtils.FORMAT_SHOW_YEAR; final long millis = calendar.getTimeInMillis(); String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); mMonthName.setText(newMonthName); mMonthName.invalidate(); }
Returns:Returns the number of weeks between the current date and the mMinDate.
/** * @return Returns the number of weeks between the current <code>date</code> * and the <code>mMinDate</code>. */
private int getWeeksSinceMinDate(Calendar date) { if (date.before(mMinDate)) { throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() + " does not precede toDate: " + date.getTime()); } long endTimeMillis = date.getTimeInMillis() + date.getTimeZone().getOffset(date.getTimeInMillis()); long startTimeMillis = mMinDate.getTimeInMillis() + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) * MILLIS_IN_DAY; return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); }
Command responsible for acting upon scroll state changes.
/** * Command responsible for acting upon scroll state changes. */
private class ScrollStateRunnable implements Runnable { private AbsListView mView; private int mNewState;
Sets up the runnable with a short delay in case the scroll state immediately changes again.
Params:
  • view – The list view that changed state
  • scrollState – The new state it changed to
/** * Sets up the runnable with a short delay in case the scroll state * immediately changes again. * * @param view The list view that changed state * @param scrollState The new state it changed to */
public void doScrollStateChange(AbsListView view, int scrollState) { mView = view; mNewState = scrollState; mDelegator.removeCallbacks(this); mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); } public void run() { mCurrentScrollState = mNewState; // Fix the position after a scroll or a fling ends if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { View child = mView.getChildAt(0); if (child == null) { // The view is no longer visible, just return return; } int dist = child.getBottom() - mListScrollTopOffset; if (dist > mListScrollTopOffset) { if (mIsScrollingUp) { mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION); } else { mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); } } } mPreviousScrollState = mNewState; } }

This is a specialized adapter for creating a list of weeks with selectable days. It can be configured to display the week number, start the week on a given day, show a reduced number of days, or display an arbitrary number of weeks at a time.

/** * <p> * This is a specialized adapter for creating a list of weeks with * selectable days. It can be configured to display the week number, start * the week on a given day, show a reduced number of days, or display an * arbitrary number of weeks at a time. * </p> */
private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener { private int mSelectedWeek; private GestureDetector mGestureDetector; private int mFocusedMonth; private final Calendar mSelectedDate = Calendar.getInstance(); private int mTotalWeekCount; public WeeksAdapter(Context context) { mContext = context; mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener()); init(); }
Set up the gesture detector and selected time
/** * Set up the gesture detector and selected time */
private void init() { mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { mTotalWeekCount++; } notifyDataSetChanged(); }
Updates the selected day and related parameters.
Params:
  • selectedDay – The time to highlight
/** * Updates the selected day and related parameters. * * @param selectedDay The time to highlight */
public void setSelectedDay(Calendar selectedDay) { if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { return; } mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); mFocusedMonth = mSelectedDate.get(Calendar.MONTH); notifyDataSetChanged(); }
Returns:The selected day of month.
/** * @return The selected day of month. */
public Calendar getSelectedDay() { return mSelectedDate; } @Override public int getCount() { return mTotalWeekCount; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { WeekView weekView = null; if (convertView != null) { weekView = (WeekView) convertView; } else { weekView = new WeekView(mContext); AbsListView.LayoutParams params = new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); weekView.setLayoutParams(params); weekView.setClickable(true); weekView.setOnTouchListener(this); } int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( Calendar.DAY_OF_WEEK) : -1; weekView.init(position, selectedWeekDay, mFocusedMonth); return weekView; }
Changes which month is in focus and updates the view.
Params:
  • month – The month to show as in focus [0-11]
/** * Changes which month is in focus and updates the view. * * @param month The month to show as in focus [0-11] */
public void setFocusMonth(int month) { if (mFocusedMonth == month) { return; } mFocusedMonth = month; notifyDataSetChanged(); } @Override public boolean onTouch(View v, MotionEvent event) { if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { WeekView weekView = (WeekView) v; // if we cannot find a day for the given location we are done if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { return true; } // it is possible that the touched day is outside the valid range // we draw whole weeks but range end can fall not on the week end if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { return true; } onDateTapped(mTempDate); return true; } return false; }
Maintains the same hour/min/sec but moves the day to the tapped day.
Params:
  • day – The day that was tapped
/** * Maintains the same hour/min/sec but moves the day to the tapped day. * * @param day The day that was tapped */
private void onDateTapped(Calendar day) { setSelectedDay(day); setMonthDisplayed(day); }
This is here so we can identify single tap events and set the selected day correctly
/** * This is here so we can identify single tap events and set the * selected day correctly */
class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { return true; } } }

This is a dynamic view for drawing a single week. It can be configured to display the week number, start the week on a given day, or show a reduced number of days. It is intended for use as a single view within a ListView. See WeeksAdapter for usage.

/** * <p> * This is a dynamic view for drawing a single week. It can be configured to * display the week number, start the week on a given day, or show a reduced * number of days. It is intended for use as a single view within a * ListView. See {@link WeeksAdapter} for usage. * </p> */
private class WeekView extends View { private final Rect mTempRect = new Rect(); private final Paint mDrawPaint = new Paint(); private final Paint mMonthNumDrawPaint = new Paint(); // Cache the number strings so we don't have to recompute them each time private String[] mDayNumbers; // Quick lookup for checking which days are in the focus month private boolean[] mFocusDay; // Whether this view has a focused day. private boolean mHasFocusedDay; // Whether this view has only focused days. private boolean mHasUnfocusedDay; // The first day displayed by this item private Calendar mFirstDay; // The month of the first day in this week private int mMonthOfFirstWeekDay = -1; // The month of the last day in this week private int mLastWeekDayMonth = -1; // The position of this week, equivalent to weeks since the week of Jan // 1st, 1900 private int mWeek = -1; // Quick reference to the width of this view, matches parent private int mWidth; // The height this view should draw at in pixels, set by height param private int mHeight; // If this view contains the selected day private boolean mHasSelectedDay = false; // Which day is selected [0-6] or -1 if no day is selected private int mSelectedDay = -1; // The number of days + a spot for week number if it is displayed private int mNumCells; // The left edge of the selected day private int mSelectedLeft = -1; // The right edge of the selected day private int mSelectedRight = -1; public WeekView(Context context) { super(context); // Sets up any standard paints that will be used initializePaints(); }
Initializes this week view.
Params:
  • weekNumber – The number of the week this view represents. The week number is a zero based index of the weeks since CalendarView.getMinDate().
  • selectedWeekDay – The selected day of the week from 0 to 6, -1 if no selected day.
  • focusedMonth – The month that is currently in focus i.e. highlighted.
/** * Initializes this week view. * * @param weekNumber The number of the week this view represents. The * week number is a zero based index of the weeks since * {@link android.widget.CalendarView#getMinDate()}. * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no * selected day. * @param focusedMonth The month that is currently in focus i.e. * highlighted. */
public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { mSelectedDay = selectedWeekDay; mHasSelectedDay = mSelectedDay != -1; mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; mWeek = weekNumber; mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); // Allocate space for caching the day numbers and focus values mDayNumbers = new String[mNumCells]; mFocusDay = new boolean[mNumCells]; // If we're showing the week number calculate it based on Monday int i = 0; if (mShowWeekNumber) { mDayNumbers[0] = String.format(Locale.getDefault(), "%d", mTempDate.get(Calendar.WEEK_OF_YEAR)); i++; } // Now adjust our starting day based on the start day of the week int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); mTempDate.add(Calendar.DAY_OF_MONTH, diff); mFirstDay = (Calendar) mTempDate.clone(); mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); mHasUnfocusedDay = true; for (; i < mNumCells; i++) { final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); mFocusDay[i] = isFocusedDay; mHasFocusedDay |= isFocusedDay; mHasUnfocusedDay &= !isFocusedDay; // do not draw dates outside the valid range to avoid user confusion if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { mDayNumbers[i] = ""; } else { mDayNumbers[i] = String.format(Locale.getDefault(), "%d", mTempDate.get(Calendar.DAY_OF_MONTH)); } mTempDate.add(Calendar.DAY_OF_MONTH, 1); } // We do one extra add at the end of the loop, if that pushed us to // new month undo it if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { mTempDate.add(Calendar.DAY_OF_MONTH, -1); } mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); updateSelectionPositions(); }
Initialize the paint instances.
/** * Initialize the paint instances. */
private void initializePaints() { mDrawPaint.setFakeBoldText(false); mDrawPaint.setAntiAlias(true); mDrawPaint.setStyle(Paint.Style.FILL); mMonthNumDrawPaint.setFakeBoldText(true); mMonthNumDrawPaint.setAntiAlias(true); mMonthNumDrawPaint.setStyle(Paint.Style.FILL); mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER); mMonthNumDrawPaint.setTextSize(mDateTextSize); }
Returns the month of the first day in this week.
Returns:The month the first day of this view is in.
/** * Returns the month of the first day in this week. * * @return The month the first day of this view is in. */
public int getMonthOfFirstWeekDay() { return mMonthOfFirstWeekDay; }
Returns the month of the last day in this week
Returns:The month the last day of this view is in
/** * Returns the month of the last day in this week * * @return The month the last day of this view is in */
public int getMonthOfLastWeekDay() { return mLastWeekDayMonth; }
Returns the first day in this view.
Returns:The first day in the view.
/** * Returns the first day in this view. * * @return The first day in the view. */
public Calendar getFirstDay() { return mFirstDay; }
Calculates the day that the given x position is in, accounting for week number.
Params:
  • x – The x position of the touch event.
Returns:True if a day was found for the given location.
/** * Calculates the day that the given x position is in, accounting for * week number. * * @param x The x position of the touch event. * @return True if a day was found for the given location. */
public boolean getDayFromLocation(float x, Calendar outCalendar) { final boolean isLayoutRtl = isLayoutRtl(); int start; int end; if (isLayoutRtl) { start = 0; end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; } else { start = mShowWeekNumber ? mWidth / mNumCells : 0; end = mWidth; } if (x < start || x > end) { outCalendar.clear(); return false; } // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); if (isLayoutRtl) { dayPosition = mDaysPerWeek - 1 - dayPosition; } outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); return true; } public boolean getBoundsForDate(Calendar date, Rect outBounds) { Calendar currDay = Calendar.getInstance(); currDay.setTime(mFirstDay.getTime()); for (int i = 0; i < mDaysPerWeek; i++) { if ((date.get(Calendar.YEAR) == currDay.get(Calendar.YEAR)) && (date.get(Calendar.MONTH) == currDay.get(Calendar.MONTH)) && (date.get(Calendar.DAY_OF_MONTH) == currDay.get(Calendar.DAY_OF_MONTH))) { // We found the matching date. Follow the logic in the draw pass that divides // the available horizontal space equally between all the entries in this week. // Note that if we're showing week number, the start entry will be that number. int cellSize = mWidth / mNumCells; if (isLayoutRtl()) { outBounds.left = cellSize * (mShowWeekNumber ? (mNumCells - i - 2) : (mNumCells - i - 1)); } else { outBounds.left = cellSize * (mShowWeekNumber ? i + 1 : i); } outBounds.top = 0; outBounds.right = outBounds.left + cellSize; outBounds.bottom = getHeight(); return true; } // Add one day currDay.add(Calendar.DAY_OF_MONTH, 1); } return false; } @Override protected void onDraw(Canvas canvas) { drawBackground(canvas); drawWeekNumbersAndDates(canvas); drawWeekSeparators(canvas); drawSelectedDateVerticalBars(canvas); }
This draws the selection highlight if a day is selected in this week.
Params:
  • canvas – The canvas to draw on
/** * This draws the selection highlight if a day is selected in this week. * * @param canvas The canvas to draw on */
private void drawBackground(Canvas canvas) { if (!mHasSelectedDay) { return; } mDrawPaint.setColor(mSelectedWeekBackgroundColor); mTempRect.top = mWeekSeparatorLineWidth; mTempRect.bottom = mHeight; final boolean isLayoutRtl = isLayoutRtl(); if (isLayoutRtl) { mTempRect.left = 0; mTempRect.right = mSelectedLeft - 2; } else { mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; mTempRect.right = mSelectedLeft - 2; } canvas.drawRect(mTempRect, mDrawPaint); if (isLayoutRtl) { mTempRect.left = mSelectedRight + 3; mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; } else { mTempRect.left = mSelectedRight + 3; mTempRect.right = mWidth; } canvas.drawRect(mTempRect, mDrawPaint); }
Draws the week and month day numbers for this week.
Params:
  • canvas – The canvas to draw on
/** * Draws the week and month day numbers for this week. * * @param canvas The canvas to draw on */
private void drawWeekNumbersAndDates(Canvas canvas) { final float textHeight = mDrawPaint.getTextSize(); final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeparatorLineWidth; final int nDays = mNumCells; final int divisor = 2 * nDays; mDrawPaint.setTextAlign(Paint.Align.CENTER); mDrawPaint.setTextSize(mDateTextSize); int i = 0; if (isLayoutRtl()) { for (; i < nDays - 1; i++) { mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor : mUnfocusedMonthDateColor); int x = (2 * i + 1) * mWidth / divisor; canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); } if (mShowWeekNumber) { mDrawPaint.setColor(mWeekNumberColor); int x = mWidth - mWidth / divisor; canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); } } else { if (mShowWeekNumber) { mDrawPaint.setColor(mWeekNumberColor); int x = mWidth / divisor; canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); i++; } for (; i < nDays; i++) { mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor : mUnfocusedMonthDateColor); int x = (2 * i + 1) * mWidth / divisor; canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); } } }
Draws a horizontal line for separating the weeks.
Params:
  • canvas – The canvas to draw on.
/** * Draws a horizontal line for separating the weeks. * * @param canvas The canvas to draw on. */
private void drawWeekSeparators(Canvas canvas) { // If it is the topmost fully visible child do not draw separator line int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); if (mListView.getChildAt(0).getTop() < 0) { firstFullyVisiblePosition++; } if (firstFullyVisiblePosition == mWeek) { return; } mDrawPaint.setColor(mWeekSeparatorLineColor); mDrawPaint.setStrokeWidth(mWeekSeparatorLineWidth); float startX; float stopX; if (isLayoutRtl()) { startX = 0; stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; } else { startX = mShowWeekNumber ? mWidth / mNumCells : 0; stopX = mWidth; } canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); }
Draws the selected date bars if this week has a selected day.
Params:
  • canvas – The canvas to draw on
/** * Draws the selected date bars if this week has a selected day. * * @param canvas The canvas to draw on */
private void drawSelectedDateVerticalBars(Canvas canvas) { if (!mHasSelectedDay) { return; } mSelectedDateVerticalBar.setBounds( mSelectedLeft - mSelectedDateVerticalBarWidth / 2, mWeekSeparatorLineWidth, mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight); mSelectedDateVerticalBar.draw(canvas); mSelectedDateVerticalBar.setBounds( mSelectedRight - mSelectedDateVerticalBarWidth / 2, mWeekSeparatorLineWidth, mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight); mSelectedDateVerticalBar.draw(canvas); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidth = w; updateSelectionPositions(); }
This calculates the positions for the selected day lines.
/** * This calculates the positions for the selected day lines. */
private void updateSelectionPositions() { if (mHasSelectedDay) { final boolean isLayoutRtl = isLayoutRtl(); int selectedPosition = mSelectedDay - mFirstDayOfWeek; if (selectedPosition < 0) { selectedPosition += 7; } if (mShowWeekNumber && !isLayoutRtl) { selectedPosition++; } if (isLayoutRtl) { mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; } else { mSelectedLeft = selectedPosition * mWidth / mNumCells; } mSelectedRight = mSelectedLeft + mWidth / mNumCells; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView .getPaddingBottom()) / mShownWeekCount; setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); } } }