| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.widget; |
| |
| import android.content.Context; |
| import android.content.res.ColorStateList; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| 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.Typeface; |
| import android.os.Bundle; |
| import android.text.format.DateFormat; |
| import android.text.format.DateUtils; |
| import android.text.format.Time; |
| import android.util.AttributeSet; |
| import android.util.MathUtils; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| |
| import com.android.internal.R; |
| import com.android.internal.widget.ExploreByTouchHelper; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Formatter; |
| import java.util.List; |
| import java.util.Locale; |
| |
| /** |
| * A calendar-like view displaying a specified month and the appropriate selectable day numbers |
| * within the specified month. |
| */ |
| class SimpleMonthView extends View { |
| private static final String TAG = "SimpleMonthView"; |
| |
| private static final int DEFAULT_HEIGHT = 32; |
| private static final int MIN_HEIGHT = 10; |
| |
| private static final int DEFAULT_SELECTED_DAY = -1; |
| private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; |
| private static final int DEFAULT_NUM_DAYS = 7; |
| private static final int DEFAULT_NUM_ROWS = 6; |
| private static final int MAX_NUM_ROWS = 6; |
| |
| private static final int SELECTED_CIRCLE_ALPHA = 60; |
| |
| private static final int DAY_SEPARATOR_WIDTH = 1; |
| |
| private final int mMiniDayNumberTextSize; |
| private final int mMonthLabelTextSize; |
| private final int mMonthDayLabelTextSize; |
| private final int mMonthHeaderSize; |
| private final int mDaySelectedCircleSize; |
| |
| // used for scaling to the device density |
| private static float mScale = 0; |
| |
| /** Single-letter (when available) formatter for the day of week label. */ |
| private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault()); |
| |
| // affects the padding on the sides of this view |
| private int mPadding = 0; |
| |
| private String mDayOfWeekTypeface; |
| private String mMonthTitleTypeface; |
| |
| private Paint mDayNumberPaint; |
| private Paint mDayNumberDisabledPaint; |
| private Paint mDayNumberSelectedPaint; |
| |
| private Paint mMonthTitlePaint; |
| private Paint mMonthDayLabelPaint; |
| |
| private final Formatter mFormatter; |
| private final StringBuilder mStringBuilder; |
| |
| private int mMonth; |
| private int mYear; |
| |
| // 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 mRowHeight = DEFAULT_HEIGHT; |
| |
| // If this view contains the today |
| private boolean mHasToday = false; |
| |
| // Which day is selected [0-6] or -1 if no day is selected |
| private int mSelectedDay = -1; |
| |
| // Which day is today [0-6] or -1 if no day is today |
| private int mToday = DEFAULT_SELECTED_DAY; |
| |
| // Which day of the week to start on [0-6] |
| private int mWeekStart = DEFAULT_WEEK_START; |
| |
| // How many days to display |
| private int mNumDays = DEFAULT_NUM_DAYS; |
| |
| // The number of days + a spot for week number if it is displayed |
| private int mNumCells = mNumDays; |
| |
| private int mDayOfWeekStart = 0; |
| |
| // First enabled day |
| private int mEnabledDayStart = 1; |
| |
| // Last enabled day |
| private int mEnabledDayEnd = 31; |
| |
| private final Calendar mCalendar = Calendar.getInstance(); |
| private final Calendar mDayLabelCalendar = Calendar.getInstance(); |
| |
| private final MonthViewTouchHelper mTouchHelper; |
| |
| private int mNumRows = DEFAULT_NUM_ROWS; |
| |
| // Optional listener for handling day click actions |
| private OnDayClickListener mOnDayClickListener; |
| |
| // Whether to prevent setting the accessibility delegate |
| private boolean mLockAccessibilityDelegate; |
| |
| private int mNormalTextColor; |
| private int mDisabledTextColor; |
| private int mSelectedDayColor; |
| |
| public SimpleMonthView(Context context) { |
| this(context, null); |
| } |
| |
| public SimpleMonthView(Context context, AttributeSet attrs) { |
| this(context, attrs, R.attr.datePickerStyle); |
| } |
| |
| public SimpleMonthView(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs); |
| |
| final Resources res = context.getResources(); |
| |
| mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); |
| mMonthTitleTypeface = res.getString(R.string.sans_serif); |
| |
| mStringBuilder = new StringBuilder(50); |
| mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); |
| |
| mMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size); |
| mMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size); |
| mMonthDayLabelTextSize = res.getDimensionPixelSize( |
| R.dimen.datepicker_month_day_label_text_size); |
| mMonthHeaderSize = res.getDimensionPixelOffset( |
| R.dimen.datepicker_month_list_item_header_height); |
| mDaySelectedCircleSize = res.getDimensionPixelSize( |
| R.dimen.datepicker_day_number_select_circle_radius); |
| |
| mRowHeight = (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height) |
| - mMonthHeaderSize) / MAX_NUM_ROWS; |
| |
| // Set up accessibility components. |
| mTouchHelper = new MonthViewTouchHelper(this); |
| setAccessibilityDelegate(mTouchHelper); |
| setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); |
| mLockAccessibilityDelegate = true; |
| |
| // Sets up any standard paints that will be used |
| initView(); |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| |
| mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale); |
| } |
| |
| void setTextColor(ColorStateList colors) { |
| final Resources res = getContext().getResources(); |
| |
| mNormalTextColor = colors.getColorForState(ENABLED_STATE_SET, |
| res.getColor(R.color.datepicker_default_normal_text_color_holo_light)); |
| mMonthTitlePaint.setColor(mNormalTextColor); |
| mMonthDayLabelPaint.setColor(mNormalTextColor); |
| |
| mDisabledTextColor = colors.getColorForState(EMPTY_STATE_SET, |
| res.getColor(R.color.datepicker_default_disabled_text_color_holo_light)); |
| mDayNumberDisabledPaint.setColor(mDisabledTextColor); |
| |
| mSelectedDayColor = colors.getColorForState(ENABLED_SELECTED_STATE_SET, |
| res.getColor(R.color.holo_blue_light)); |
| mDayNumberSelectedPaint.setColor(mSelectedDayColor); |
| mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA); |
| } |
| |
| @Override |
| public void setAccessibilityDelegate(AccessibilityDelegate delegate) { |
| // Workaround for a JB MR1 issue where accessibility delegates on |
| // top-level ListView items are overwritten. |
| if (!mLockAccessibilityDelegate) { |
| super.setAccessibilityDelegate(delegate); |
| } |
| } |
| |
| public void setOnDayClickListener(OnDayClickListener listener) { |
| mOnDayClickListener = listener; |
| } |
| |
| @Override |
| public boolean dispatchHoverEvent(MotionEvent event) { |
| // First right-of-refusal goes the touch exploration helper. |
| if (mTouchHelper.dispatchHoverEvent(event)) { |
| return true; |
| } |
| return super.dispatchHoverEvent(event); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_UP: |
| final int day = getDayFromLocation(event.getX(), event.getY()); |
| if (day >= 0) { |
| onDayClick(day); |
| } |
| break; |
| } |
| return true; |
| } |
| |
| /** |
| * Sets up the text and style properties for painting. |
| */ |
| private void initView() { |
| mMonthTitlePaint = new Paint(); |
| mMonthTitlePaint.setAntiAlias(true); |
| mMonthTitlePaint.setColor(mNormalTextColor); |
| mMonthTitlePaint.setTextSize(mMonthLabelTextSize); |
| mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD)); |
| mMonthTitlePaint.setTextAlign(Align.CENTER); |
| mMonthTitlePaint.setStyle(Style.FILL); |
| mMonthTitlePaint.setFakeBoldText(true); |
| |
| mMonthDayLabelPaint = new Paint(); |
| mMonthDayLabelPaint.setAntiAlias(true); |
| mMonthDayLabelPaint.setColor(mNormalTextColor); |
| mMonthDayLabelPaint.setTextSize(mMonthDayLabelTextSize); |
| mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL)); |
| mMonthDayLabelPaint.setTextAlign(Align.CENTER); |
| mMonthDayLabelPaint.setStyle(Style.FILL); |
| mMonthDayLabelPaint.setFakeBoldText(true); |
| |
| mDayNumberSelectedPaint = new Paint(); |
| mDayNumberSelectedPaint.setAntiAlias(true); |
| mDayNumberSelectedPaint.setColor(mSelectedDayColor); |
| mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA); |
| mDayNumberSelectedPaint.setTextAlign(Align.CENTER); |
| mDayNumberSelectedPaint.setStyle(Style.FILL); |
| mDayNumberSelectedPaint.setFakeBoldText(true); |
| |
| mDayNumberPaint = new Paint(); |
| mDayNumberPaint.setAntiAlias(true); |
| mDayNumberPaint.setTextSize(mMiniDayNumberTextSize); |
| mDayNumberPaint.setTextAlign(Align.CENTER); |
| mDayNumberPaint.setStyle(Style.FILL); |
| mDayNumberPaint.setFakeBoldText(false); |
| |
| mDayNumberDisabledPaint = new Paint(); |
| mDayNumberDisabledPaint.setAntiAlias(true); |
| mDayNumberDisabledPaint.setColor(mDisabledTextColor); |
| mDayNumberDisabledPaint.setTextSize(mMiniDayNumberTextSize); |
| mDayNumberDisabledPaint.setTextAlign(Align.CENTER); |
| mDayNumberDisabledPaint.setStyle(Style.FILL); |
| mDayNumberDisabledPaint.setFakeBoldText(false); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| drawMonthTitle(canvas); |
| drawWeekDayLabels(canvas); |
| drawDays(canvas); |
| } |
| |
| private static boolean isValidDayOfWeek(int day) { |
| return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY; |
| } |
| |
| private static boolean isValidMonth(int month) { |
| return month >= Calendar.JANUARY && month <= Calendar.DECEMBER; |
| } |
| |
| /** |
| * Sets all the parameters for displaying this week. Parameters have a default value and |
| * will only update if a new value is included, except for focus month, which will always |
| * default to no focus month if no value is passed in. The only required parameter is the |
| * week start. |
| * |
| * @param selectedDay the selected day of the month, or -1 for no selection. |
| * @param month the month. |
| * @param year the year. |
| * @param weekStart which day the week should start on. {@link Calendar#SUNDAY} through |
| * {@link Calendar#SATURDAY}. |
| * @param enabledDayStart the first enabled day. |
| * @param enabledDayEnd the last enabled day. |
| */ |
| void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, |
| int enabledDayEnd) { |
| if (mRowHeight < MIN_HEIGHT) { |
| mRowHeight = MIN_HEIGHT; |
| } |
| |
| mSelectedDay = selectedDay; |
| |
| if (isValidMonth(month)) { |
| mMonth = month; |
| } |
| mYear = year; |
| |
| // Figure out what day today is |
| final Time today = new Time(Time.getCurrentTimezone()); |
| today.setToNow(); |
| mHasToday = false; |
| mToday = -1; |
| |
| mCalendar.set(Calendar.MONTH, mMonth); |
| mCalendar.set(Calendar.YEAR, mYear); |
| mCalendar.set(Calendar.DAY_OF_MONTH, 1); |
| mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK); |
| |
| if (isValidDayOfWeek(weekStart)) { |
| mWeekStart = weekStart; |
| } else { |
| mWeekStart = mCalendar.getFirstDayOfWeek(); |
| } |
| |
| if (enabledDayStart > 0 && enabledDayEnd < 32) { |
| mEnabledDayStart = enabledDayStart; |
| } |
| if (enabledDayEnd > 0 && enabledDayEnd < 32 && enabledDayEnd >= enabledDayStart) { |
| mEnabledDayEnd = enabledDayEnd; |
| } |
| |
| mNumCells = getDaysInMonth(mMonth, mYear); |
| for (int i = 0; i < mNumCells; i++) { |
| final int day = i + 1; |
| if (sameDay(day, today)) { |
| mHasToday = true; |
| mToday = day; |
| } |
| } |
| mNumRows = calculateNumRows(); |
| |
| // Invalidate cached accessibility information. |
| mTouchHelper.invalidateRoot(); |
| } |
| |
| private static int getDaysInMonth(int month, int year) { |
| switch (month) { |
| case Calendar.JANUARY: |
| case Calendar.MARCH: |
| case Calendar.MAY: |
| case Calendar.JULY: |
| case Calendar.AUGUST: |
| case Calendar.OCTOBER: |
| case Calendar.DECEMBER: |
| return 31; |
| case Calendar.APRIL: |
| case Calendar.JUNE: |
| case Calendar.SEPTEMBER: |
| case Calendar.NOVEMBER: |
| return 30; |
| case Calendar.FEBRUARY: |
| return (year % 4 == 0) ? 29 : 28; |
| default: |
| throw new IllegalArgumentException("Invalid Month"); |
| } |
| } |
| |
| public void reuse() { |
| mNumRows = DEFAULT_NUM_ROWS; |
| requestLayout(); |
| } |
| |
| private int calculateNumRows() { |
| int offset = findDayOffset(); |
| int dividend = (offset + mNumCells) / mNumDays; |
| int remainder = (offset + mNumCells) % mNumDays; |
| return (dividend + (remainder > 0 ? 1 : 0)); |
| } |
| |
| private boolean sameDay(int day, Time today) { |
| return mYear == today.year && |
| mMonth == today.month && |
| day == today.monthDay; |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows |
| + mMonthHeaderSize); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| mWidth = w; |
| |
| // Invalidate cached accessibility information. |
| mTouchHelper.invalidateRoot(); |
| } |
| |
| private String getMonthAndYearString() { |
| int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
| | DateUtils.FORMAT_NO_MONTH_DAY; |
| mStringBuilder.setLength(0); |
| long millis = mCalendar.getTimeInMillis(); |
| return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags, |
| Time.getCurrentTimezone()).toString(); |
| } |
| |
| private void drawMonthTitle(Canvas canvas) { |
| final float x = (mWidth + 2 * mPadding) / 2f; |
| final float y = (mMonthHeaderSize - mMonthDayLabelTextSize) / 2f; |
| canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint); |
| } |
| |
| private void drawWeekDayLabels(Canvas canvas) { |
| final int y = mMonthHeaderSize - (mMonthDayLabelTextSize / 2); |
| final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); |
| |
| for (int i = 0; i < mNumDays; i++) { |
| final int calendarDay = (i + mWeekStart) % mNumDays; |
| mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); |
| |
| final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime()); |
| final int x = (2 * i + 1) * dayWidthHalf + mPadding; |
| canvas.drawText(dayLabel, x, y, mMonthDayLabelPaint); |
| } |
| } |
| |
| /** |
| * Draws the month days. |
| */ |
| private void drawDays(Canvas canvas) { |
| int y = (((mRowHeight + mMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH) |
| + mMonthHeaderSize; |
| int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); |
| int j = findDayOffset(); |
| for (int day = 1; day <= mNumCells; day++) { |
| int x = (2 * j + 1) * dayWidthHalf + mPadding; |
| if (mSelectedDay == day) { |
| canvas.drawCircle(x, y - (mMiniDayNumberTextSize / 3), mDaySelectedCircleSize, |
| mDayNumberSelectedPaint); |
| } |
| |
| if (mHasToday && mToday == day) { |
| mDayNumberPaint.setColor(mSelectedDayColor); |
| } else { |
| mDayNumberPaint.setColor(mNormalTextColor); |
| } |
| final Paint paint = (day < mEnabledDayStart || day > mEnabledDayEnd) ? |
| mDayNumberDisabledPaint : mDayNumberPaint; |
| canvas.drawText(String.format("%d", day), x, y, paint); |
| j++; |
| if (j == mNumDays) { |
| j = 0; |
| y += mRowHeight; |
| } |
| } |
| } |
| |
| private int findDayOffset() { |
| return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart) |
| - mWeekStart; |
| } |
| |
| /** |
| * Calculates the day that the given x position is in, accounting for week |
| * number. Returns the day or -1 if the position wasn't in a day. |
| * |
| * @param x The x position of the touch event |
| * @return The day number, or -1 if the position wasn't in a day |
| */ |
| private int getDayFromLocation(float x, float y) { |
| int dayStart = mPadding; |
| if (x < dayStart || x > mWidth - mPadding) { |
| return -1; |
| } |
| // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels |
| int row = (int) (y - mMonthHeaderSize) / mRowHeight; |
| int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); |
| |
| int day = column - findDayOffset() + 1; |
| day += row * mNumDays; |
| if (day < 1 || day > mNumCells) { |
| return -1; |
| } |
| return day; |
| } |
| |
| /** |
| * Called when the user clicks on a day. Handles callbacks to the |
| * {@link OnDayClickListener} if one is set. |
| * |
| * @param day The day that was clicked |
| */ |
| private void onDayClick(int day) { |
| if (mOnDayClickListener != null) { |
| Calendar date = Calendar.getInstance(); |
| date.set(mYear, mMonth, day); |
| mOnDayClickListener.onDayClick(this, date); |
| } |
| |
| // This is a no-op if accessibility is turned off. |
| mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED); |
| } |
| |
| /** |
| * @return The date that has accessibility focus, or {@code null} if no date |
| * has focus |
| */ |
| Calendar getAccessibilityFocus() { |
| final int day = mTouchHelper.getFocusedVirtualView(); |
| Calendar date = null; |
| if (day >= 0) { |
| date = Calendar.getInstance(); |
| date.set(mYear, mMonth, day); |
| } |
| return date; |
| } |
| |
| /** |
| * Clears accessibility focus within the view. No-op if the view does not |
| * contain accessibility focus. |
| */ |
| public void clearAccessibilityFocus() { |
| mTouchHelper.clearFocusedVirtualView(); |
| } |
| |
| /** |
| * Attempts to restore accessibility focus to the specified date. |
| * |
| * @param day The date which should receive focus |
| * @return {@code false} if the date is not valid for this month view, or |
| * {@code true} if the date received focus |
| */ |
| boolean restoreAccessibilityFocus(Calendar day) { |
| if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) || |
| (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) { |
| return false; |
| } |
| mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH)); |
| return true; |
| } |
| |
| /** |
| * Provides a virtual view hierarchy for interfacing with an accessibility |
| * service. |
| */ |
| private class MonthViewTouchHelper extends ExploreByTouchHelper { |
| private static final String DATE_FORMAT = "dd MMMM yyyy"; |
| |
| private final Rect mTempRect = new Rect(); |
| private final Calendar mTempCalendar = Calendar.getInstance(); |
| |
| public MonthViewTouchHelper(View host) { |
| super(host); |
| } |
| |
| public void setFocusedVirtualView(int virtualViewId) { |
| getAccessibilityNodeProvider(SimpleMonthView.this).performAction( |
| virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); |
| } |
| |
| public void clearFocusedVirtualView() { |
| final int focusedVirtualView = getFocusedVirtualView(); |
| if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) { |
| getAccessibilityNodeProvider(SimpleMonthView.this).performAction( |
| focusedVirtualView, |
| AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, |
| null); |
| } |
| } |
| |
| @Override |
| protected int getVirtualViewAt(float x, float y) { |
| final int day = getDayFromLocation(x, y); |
| if (day >= 0) { |
| return day; |
| } |
| return ExploreByTouchHelper.INVALID_ID; |
| } |
| |
| @Override |
| protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { |
| for (int day = 1; day <= mNumCells; day++) { |
| virtualViewIds.add(day); |
| } |
| } |
| |
| @Override |
| protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { |
| event.setContentDescription(getItemDescription(virtualViewId)); |
| } |
| |
| @Override |
| protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { |
| getItemBounds(virtualViewId, mTempRect); |
| |
| node.setContentDescription(getItemDescription(virtualViewId)); |
| node.setBoundsInParent(mTempRect); |
| node.addAction(AccessibilityNodeInfo.ACTION_CLICK); |
| |
| if (virtualViewId == mSelectedDay) { |
| node.setSelected(true); |
| } |
| |
| } |
| |
| @Override |
| protected boolean onPerformActionForVirtualView(int virtualViewId, int action, |
| Bundle arguments) { |
| switch (action) { |
| case AccessibilityNodeInfo.ACTION_CLICK: |
| onDayClick(virtualViewId); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Calculates the bounding rectangle of a given time object. |
| * |
| * @param day The day to calculate bounds for |
| * @param rect The rectangle in which to store the bounds |
| */ |
| private void getItemBounds(int day, Rect rect) { |
| final int offsetX = mPadding; |
| final int offsetY = mMonthHeaderSize; |
| final int cellHeight = mRowHeight; |
| final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays); |
| final int index = ((day - 1) + findDayOffset()); |
| final int row = (index / mNumDays); |
| final int column = (index % mNumDays); |
| final int x = (offsetX + (column * cellWidth)); |
| final int y = (offsetY + (row * cellHeight)); |
| |
| rect.set(x, y, (x + cellWidth), (y + cellHeight)); |
| } |
| |
| /** |
| * Generates a description for a given time object. Since this |
| * description will be spoken, the components are ordered by descending |
| * specificity as DAY MONTH YEAR. |
| * |
| * @param day The day to generate a description for |
| * @return A description of the time object |
| */ |
| private CharSequence getItemDescription(int day) { |
| mTempCalendar.set(mYear, mMonth, day); |
| final CharSequence date = DateFormat.format(DATE_FORMAT, |
| mTempCalendar.getTimeInMillis()); |
| |
| if (day == mSelectedDay) { |
| return getContext().getString(R.string.item_is_selected, date); |
| } |
| |
| return date; |
| } |
| } |
| |
| /** |
| * Handles callbacks when the user clicks on a time object. |
| */ |
| public interface OnDayClickListener { |
| public void onDayClick(SimpleMonthView view, Calendar day); |
| } |
| } |