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