android.widget.CalendarView Maven / Gradle / Ivy
Show all versions of android-all Show documentation
/*
* Copyright (C) 2010 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.annotation.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.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
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 android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AbsListView.OnScrollListener;
import com.android.internal.R;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import libcore.icu.LocaleData;
/**
* This class is a calendar widget for displaying and selecting dates. The range
* of dates supported by this calendar is configurable. A user can select a date
* by taping on it and can scroll and fling the calendar to a desired date.
*
* @attr ref android.R.styleable#CalendarView_showWeekNumber
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
* @attr ref android.R.styleable#CalendarView_minDate
* @attr ref android.R.styleable#CalendarView_maxDate
* @attr ref android.R.styleable#CalendarView_shownWeekCount
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
* @attr ref android.R.styleable#CalendarView_weekNumberColor
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
@Widget
public class CalendarView extends FrameLayout {
/**
* Tag for logging.
*/
private static final String LOG_TAG = CalendarView.class.getSimpleName();
/**
* Default value whether to show week number.
*/
private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
/**
* The number of milliseconds in a day.e
*/
private static final long MILLIS_IN_DAY = 86400000L;
/**
* The number of day in a week.
*/
private static final int DAYS_PER_WEEK = 7;
/**
* 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
*/
private static final int SCROLL_HYST_WEEKS = 2;
/**
* 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.
*/
private static final int ADJUSTMENT_SCROLL_DURATION = 500;
/**
* How long to wait after receiving an onScrollStateChanged notification
* before acting on it.
*/
private static final int SCROLL_CHANGE_DELAY = 40;
/**
* String for parsing dates.
*/
private static final String DATE_FORMAT = "MM/dd/yyyy";
/**
* The default minimal date.
*/
private static final String DEFAULT_MIN_DATE = "01/01/1900";
/**
* The default maximal date.
*/
private static final String DEFAULT_MAX_DATE = "01/01/2100";
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 mWeekSeperatorLineWidth;
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.
*/
private int mListScrollTopOffset = 2;
/**
* The visible height of a week view.
*/
private int mWeekMinVisibleHeight = 12;
/**
* The visible height of a week view.
*/
private int mBottomBuffer = 20;
/**
* The number of shown weeks.
*/
private int mShownWeekCount;
/**
* Flag whether to show the week number.
*/
private boolean mShowWeekNumber;
/**
* The number of day per week to be shown.
*/
private int mDaysPerWeek = 7;
/**
* The friction of the week list while flinging.
*/
private float mFriction = .05f;
/**
* Scale for adjusting velocity of the week list while flinging.
*/
private float mVelocityScale = 0.333f;
/**
* The adapter for the weeks list.
*/
private WeeksAdapter mAdapter;
/**
* The weeks list.
*/
private ListView mListView;
/**
* The name of the month to display.
*/
private TextView mMonthName;
/**
* The header with week day names.
*/
private ViewGroup mDayNamesHeader;
/**
* Cached labels for the week names header.
*/
private String[] mDayLabels;
/**
* The first day of the week.
*/
private int mFirstDayOfWeek;
/**
* Which month should be displayed/highlighted [0-11].
*/
private int mCurrentMonthDisplayed;
/**
* Used for tracking during a scroll.
*/
private long mPreviousScrollPosition;
/**
* Used for tracking which direction the view is scrolling.
*/
private boolean mIsScrollingUp = false;
/**
* The previous scroll state of the weeks ListView.
*/
private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
* The current scroll state of the weeks ListView.
*/
private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
* Listener for changes in the selected day.
*/
private OnDateChangeListener mOnDateChangeListener;
/**
* Command for adjusting the position after a scroll/fling.
*/
private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
/**
* Temporary instance to avoid multiple instantiations.
*/
private Calendar mTempDate;
/**
* The first day of the focused month.
*/
private Calendar mFirstDayOfMonth;
/**
* The start date of the range supported by this picker.
*/
private Calendar mMinDate;
/**
* The end date of the range supported by this picker.
*/
private Calendar mMaxDate;
/**
* Date format for parsing dates.
*/
private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
/**
* The current locale.
*/
private Locale mCurrentLocale;
/**
* The callback used to indicate the user changes the date.
*/
public interface OnDateChangeListener {
/**
* Called upon change of the selected day.
*
* @param view The view associated with this listener.
* @param year The year that was set.
* @param month The month that was set [0-11].
* @param dayOfMonth The day of the month that was set.
*/
public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth);
}
public CalendarView(Context context) {
this(context, null);
}
public CalendarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CalendarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, 0);
// initialization based on locale
setCurrentLocale(Locale.getDefault());
TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView,
R.attr.calendarViewStyle, 0);
mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
DEFAULT_SHOW_WEEK_NUMBER);
mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
LocaleData.get(Locale.getDefault()).firstDayOfWeek);
String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
parseDate(DEFAULT_MIN_DATE, mMinDate);
}
String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
parseDate(DEFAULT_MAX_DATE, mMaxDate);
}
if (mMaxDate.before(mMinDate)) {
throw new IllegalArgumentException("Max date cannot be before min date.");
}
mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
DEFAULT_SHOWN_WEEK_COUNT);
mSelectedWeekBackgroundColor = attributesArray.getColor(
R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
mFocusedMonthDateColor = attributesArray.getColor(
R.styleable.CalendarView_focusedMonthDateColor, 0);
mUnfocusedMonthDateColor = attributesArray.getColor(
R.styleable.CalendarView_unfocusedMonthDateColor, 0);
mWeekSeparatorLineColor = attributesArray.getColor(
R.styleable.CalendarView_weekSeparatorLineColor, 0);
mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
mSelectedDateVerticalBar = attributesArray.getDrawable(
R.styleable.CalendarView_selectedDateVerticalBar);
mDateTextAppearanceResId = attributesArray.getResourceId(
R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
updateDateTextSize();
mWeekDayTextAppearanceResId = attributesArray.getResourceId(
R.styleable.CalendarView_weekDayTextAppearance,
DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
attributesArray.recycle();
DisplayMetrics displayMetrics = 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);
mWeekSeperatorLineWidth = (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);
addView(content);
mListView = (ListView) findViewById(R.id.list);
mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
mMonthName = (TextView) content.findViewById(com.android.internal.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);
}
invalidate();
}
/**
* Sets the number of weeks to be shown.
*
* @param count The shown week count.
*
* @attr ref android.R.styleable#CalendarView_shownWeekCount
*/
public void setShownWeekCount(int count) {
if (mShownWeekCount != count) {
mShownWeekCount = count;
invalidate();
}
}
/**
* Gets the number of weeks to be shown.
*
* @return The shown week count.
*
* @attr ref android.R.styleable#CalendarView_shownWeekCount
*/
public int getShownWeekCount() {
return mShownWeekCount;
}
/**
* Sets the background color for the selected week.
*
* @param color The week background color.
*
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
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();
}
}
}
}
/**
* Gets the background color for the selected week.
*
* @return The week background color.
*
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
public int getSelectedWeekBackgroundColor() {
return mSelectedWeekBackgroundColor;
}
/**
* Sets the color for the dates of the focused month.
*
* @param color The focused month date color.
*
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
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();
}
}
}
}
/**
* Gets the color for the dates in the focused month.
*
* @return The focused month date color.
*
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
public int getFocusedMonthDateColor() {
return mFocusedMonthDateColor;
}
/**
* Sets the color for the dates of a not focused month.
*
* @param color A not focused month date color.
*
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
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();
}
}
}
}
/**
* Gets the color for the dates in a not focused month.
*
* @return A not focused month date color.
*
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
public int getUnfocusedMonthDateColor() {
return mFocusedMonthDateColor;
}
/**
* Sets the color for the week numbers.
*
* @param color The week number color.
*
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
public void setWeekNumberColor(int color) {
if (mWeekNumberColor != color) {
mWeekNumberColor = color;
if (mShowWeekNumber) {
invalidateAllWeekViews();
}
}
}
/**
* Gets the color for the week numbers.
*
* @return The week number color.
*
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
public int getWeekNumberColor() {
return mWeekNumberColor;
}
/**
* Sets the color for the separator line between weeks.
*
* @param color The week separator color.
*
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
public void setWeekSeparatorLineColor(int color) {
if (mWeekSeparatorLineColor != color) {
mWeekSeparatorLineColor = color;
invalidateAllWeekViews();
}
}
/**
* Gets the color for the separator line between weeks.
*
* @return The week separator color.
*
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
public int getWeekSeparatorLineColor() {
return mWeekSeparatorLineColor;
}
/**
* Sets the drawable for the vertical bar shown at the beginning and at
* the end of the selected date.
*
* @param resourceId The vertical bar drawable resource id.
*
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
public void setSelectedDateVerticalBar(int resourceId) {
Drawable drawable = getResources().getDrawable(resourceId);
setSelectedDateVerticalBar(drawable);
}
/**
* Sets the drawable for the vertical bar shown at the beginning and at
* the end of the selected date.
*
* @param drawable The vertical bar drawable.
*
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
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();
}
}
}
}
/**
* Gets the drawable for the vertical bar shown at the beginning and at
* the end of the selected date.
*
* @return The vertical bar drawable.
*/
public Drawable getSelectedDateVerticalBar() {
return mSelectedDateVerticalBar;
}
/**
* Sets the text appearance for the week day abbreviation of the calendar header.
*
* @param resourceId The text appearance resource id.
*
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
*/
public void setWeekDayTextAppearance(int resourceId) {
if (mWeekDayTextAppearanceResId != resourceId) {
mWeekDayTextAppearanceResId = resourceId;
setUpHeader();
}
}
/**
* Gets the text appearance for the week day abbreviation of the calendar header.
*
* @return The text appearance resource id.
*
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
*/
public int getWeekDayTextAppearance() {
return mWeekDayTextAppearanceResId;
}
/**
* Sets the text appearance for the calendar dates.
*
* @param resourceId The text appearance resource id.
*
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
public void setDateTextAppearance(int resourceId) {
if (mDateTextAppearanceResId != resourceId) {
mDateTextAppearanceResId = resourceId;
updateDateTextSize();
invalidateAllWeekViews();
}
}
/**
* Gets the text appearance for the calendar dates.
*
* @return The text appearance resource id.
*
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
public int getDateTextAppearance() {
return mDateTextAppearanceResId;
}
@Override
public void setEnabled(boolean enabled) {
mListView.setEnabled(enabled);
}
@Override
public boolean isEnabled() {
return mListView.isEnabled();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setCurrentLocale(newConfig.locale);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(CalendarView.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(CalendarView.class.getName());
}
/**
* Gets the minimal date supported by this {@link CalendarView} in milliseconds
* since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
* zone.
*
* Note: The default minimal date is 01/01/1900.
*
*
* @return The minimal supported date.
*
* @attr ref android.R.styleable#CalendarView_minDate
*/
public long getMinDate() {
return mMinDate.getTimeInMillis();
}
/**
* Sets the minimal date supported by this {@link CalendarView} in milliseconds
* since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
* zone.
*
* @param minDate The minimal supported date.
*
* @attr ref android.R.styleable#CalendarView_minDate
*/
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);
}
}
/**
* Gets the maximal date supported by this {@link CalendarView} in milliseconds
* since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
* zone.
*
* Note: The default maximal date is 01/01/2100.
*
*
* @return The maximal supported date.
*
* @attr ref android.R.styleable#CalendarView_maxDate
*/
public long getMaxDate() {
return mMaxDate.getTimeInMillis();
}
/**
* Sets the maximal date supported by this {@link CalendarView} in milliseconds
* since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
* zone.
*
* @param maxDate The maximal supported date.
*
* @attr ref android.R.styleable#CalendarView_maxDate
*/
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);
}
}
/**
* Sets whether to show the week number.
*
* @param showWeekNumber True to show the week number.
*
* @attr ref android.R.styleable#CalendarView_showWeekNumber
*/
public void setShowWeekNumber(boolean showWeekNumber) {
if (mShowWeekNumber == showWeekNumber) {
return;
}
mShowWeekNumber = showWeekNumber;
mAdapter.notifyDataSetChanged();
setUpHeader();
}
/**
* Gets whether to show the week number.
*
* @return True if showing the week number.
*
* @attr ref android.R.styleable#CalendarView_showWeekNumber
*/
public boolean getShowWeekNumber() {
return mShowWeekNumber;
}
/**
* Gets the first day of week.
*
* @return The first day of the week conforming to the {@link CalendarView}
* APIs.
* @see Calendar#MONDAY
* @see Calendar#TUESDAY
* @see Calendar#WEDNESDAY
* @see Calendar#THURSDAY
* @see Calendar#FRIDAY
* @see Calendar#SATURDAY
* @see Calendar#SUNDAY
*
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
*/
public int getFirstDayOfWeek() {
return mFirstDayOfWeek;
}
/**
* Sets the first day of week.
*
* @param firstDayOfWeek The first day of the week conforming to the
* {@link CalendarView} APIs.
* @see Calendar#MONDAY
* @see Calendar#TUESDAY
* @see Calendar#WEDNESDAY
* @see Calendar#THURSDAY
* @see Calendar#FRIDAY
* @see Calendar#SATURDAY
* @see Calendar#SUNDAY
*
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
*/
public void setFirstDayOfWeek(int firstDayOfWeek) {
if (mFirstDayOfWeek == firstDayOfWeek) {
return;
}
mFirstDayOfWeek = firstDayOfWeek;
mAdapter.init();
mAdapter.notifyDataSetChanged();
setUpHeader();
}
/**
* Sets the listener to be notified upon selected date change.
*
* @param listener The listener to be notified.
*/
public void setOnDateChangeListener(OnDateChangeListener listener) {
mOnDateChangeListener = listener;
}
/**
* Gets the selected date in milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @return The selected date.
*/
public long getDate() {
return mAdapter.mSelectedDate.getTimeInMillis();
}
/**
* Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @param date The selected date.
*
* @throws IllegalArgumentException of the provided date is before the
* minimal or after the maximal date.
*
* @see #setDate(long, boolean, boolean)
* @see #setMinDate(long)
* @see #setMaxDate(long)
*/
public void setDate(long date) {
setDate(date, false, false);
}
/**
* Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @param date The date.
* @param animate Whether to animate the scroll to the current date.
* @param center Whether to center the current date even if it is already visible.
*
* @throws IllegalArgumentException of the provided date is before the
* minimal or after the maximal date.
*
* @see #setMinDate(long)
* @see #setMaxDate(long)
*/
public void setDate(long date, boolean animate, boolean center) {
mTempDate.setTimeInMillis(date);
if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
return;
}
goTo(mTempDate, animate, true, center);
}
private void updateDateTextSize() {
TypedArray dateTextAppearance = getContext().obtainStyledAttributes(
mDateTextAppearanceResId, R.styleable.TextAppearance);
mDateTextSize = dateTextAppearance.getDimensionPixelSize(
R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
dateTextAppearance.recycle();
}
/**
* 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();
}
}
/**
* Sets the current locale.
*
* @param locale The current locale.
*/
private void setCurrentLocale(Locale locale) {
if (locale.equals(mCurrentLocale)) {
return;
}
mCurrentLocale = locale;
mTempDate = getCalendarForLocale(mTempDate, locale);
mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
mMinDate = getCalendarForLocale(mMinDate, locale);
mMaxDate = getCalendarForLocale(mMaxDate, locale);
}
/**
* Gets a calendar for locale bootstrapped with the value of a given calendar.
*
* @param oldCalendar The old calendar.
* @param locale The locale.
*/
private 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;
}
}
/**
* @return True if the firstDate
is the same as the
* secondDate
.
*/
private 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.
*/
private void setUpAdapter() {
if (mAdapter == null) {
mAdapter = new WeeksAdapter(getContext());
mAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
if (mOnDateChangeListener != null) {
Calendar selectedDay = mAdapter.getSelectedDay();
mOnDateChangeListener.onSelectedDayChange(CalendarView.this,
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.
*/
private void setUpHeader() {
mDayLabels = new String[mDaysPerWeek];
for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
DateUtils.LENGTH_SHORTEST);
}
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(mContext, mWeekDayTextAppearanceResId);
}
if (i < mDaysPerWeek + 1) {
label.setText(mDayLabels[i - 1]);
label.setVisibility(View.VISIBLE);
} else {
label.setVisibility(View.GONE);
}
}
mDayNamesHeader.invalidate();
}
/**
* 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 OnScrollListener() {
public void onScrollStateChanged(AbsListView view, int scrollState) {
CalendarView.this.onScrollStateChanged(view, scrollState);
}
public void onScroll(
AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
CalendarView.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.
*
* @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 of the provided date is before the
* range start of 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("Time not between " + mMinDate.getTime()
+ " and " + mMaxDate.getTime());
}
// 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 = 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, OnScrollListener.SCROLL_STATE_IDLE);
}
} else if (setSelected) {
// Otherwise just set the selection
setMonthDisplayed(date);
}
}
/**
* Parses the given date
and in case of success sets
* the result to the outDate
.
*
* @return True if the date was parsed.
*/
private boolean parseDate(String date, Calendar outDate) {
try {
outDate.setTime(mDateFormat.parse(date));
return true;
} catch (ParseException e) {
Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
return false;
}
}
/**
* Called when a view
transitions to a new scrollState
*
.
*/
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.
*/
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);
}
// 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.
*
* @param calendar A day in the new focus month.
*/
private void setMonthDisplayed(Calendar calendar) {
final int newMonthDisplayed = calendar.get(Calendar.MONTH);
if (mCurrentMonthDisplayed != newMonthDisplayed) {
mCurrentMonthDisplayed = newMonthDisplayed;
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();
}
}
/**
* @return Returns the number of weeks between the current date
* and the mMinDate
.
*/
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.
*/
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.
*
* @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;
removeCallbacks(this);
postDelayed(this, SCROLL_CHANGE_DELAY);
}
public void run() {
mCurrentScrollState = mNewState;
// Fix the position after a scroll or a fling ends
if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
&& mPreviousScrollState != 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.
*
*/
private class WeeksAdapter extends BaseAdapter implements 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 CalendarGestureListener());
init();
}
/**
* 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++;
}
}
/**
* 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();
}
/**
* @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);
android.widget.AbsListView.LayoutParams params =
new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
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.
*
* @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.
*
* @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
*/
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 {@link WeeksAdapter} for usage.
*
*/
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
initilaizePaints();
}
/**
* 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 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] = Integer.toString(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] = Integer.toString(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 isntances.
*/
private void initilaizePaints() {
mDrawPaint.setFakeBoldText(false);
mDrawPaint.setAntiAlias(true);
mDrawPaint.setStyle(Style.FILL);
mMonthNumDrawPaint.setFakeBoldText(true);
mMonthNumDrawPaint.setAntiAlias(true);
mMonthNumDrawPaint.setStyle(Style.FILL);
mMonthNumDrawPaint.setTextAlign(Align.CENTER);
}
/**
* 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
*
* @return The month the last day of this view is in
*/
public int getMonthOfLastWeekDay() {
return mLastWeekDayMonth;
}
/**
* 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.
*
* @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) {
int dayStart = mShowWeekNumber ? mWidth / mNumCells : 0;
if (x < dayStart || x > mWidth) {
outCalendar.clear();
return false;
}
// Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
int dayPosition = (int) ((x - dayStart) * mDaysPerWeek
/ (mWidth - dayStart));
outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
return true;
}
@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.
*
* @param canvas The canvas to draw on
*/
private void drawBackground(Canvas canvas) {
if (!mHasSelectedDay) {
return;
}
mDrawPaint.setColor(mSelectedWeekBackgroundColor);
mTempRect.top = mWeekSeperatorLineWidth;
mTempRect.bottom = mHeight;
mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
mTempRect.right = mSelectedLeft - 2;
canvas.drawRect(mTempRect, mDrawPaint);
mTempRect.left = mSelectedRight + 3;
mTempRect.right = mWidth;
canvas.drawRect(mTempRect, mDrawPaint);
}
/**
* Draws the week and month day numbers for this week.
*
* @param canvas The canvas to draw on
*/
private void drawWeekNumbersAndDates(Canvas canvas) {
float textHeight = mDrawPaint.getTextSize();
int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
int nDays = mNumCells;
mDrawPaint.setTextAlign(Align.CENTER);
mDrawPaint.setTextSize(mDateTextSize);
int i = 0;
int divisor = 2 * nDays;
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.
*
* @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(mWeekSeperatorLineWidth);
float x = mShowWeekNumber ? mWidth / mNumCells : 0;
canvas.drawLine(x, 0, mWidth, 0, mDrawPaint);
}
/**
* 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,
mWeekSeperatorLineWidth,
mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight);
mSelectedDateVerticalBar.draw(canvas);
mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2,
mWeekSeperatorLineWidth,
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.
*/
private void updateSelectionPositions() {
if (mHasSelectedDay) {
int selectedPosition = mSelectedDay - mFirstDayOfWeek;
if (selectedPosition < 0) {
selectedPosition += 7;
}
if (mShowWeekNumber) {
selectedPosition++;
}
mSelectedLeft = selectedPosition * mWidth / mNumCells;
mSelectedRight = (selectedPosition + 1) * mWidth / mNumCells;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
.getPaddingBottom()) / mShownWeekCount;
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
}
}
}