All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.widget.SimpleMonthView Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.Resources;
import android.content.res.TypedArray;
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.TextPaint;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.MathUtils;
import android.util.StateSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;

import com.android.internal.R;
import com.android.internal.widget.ExploreByTouchHelper;

import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
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 int DAYS_IN_WEEK = 7;
    private static final int MAX_WEEKS_IN_MONTH = 6;

    private static final int DEFAULT_SELECTED_DAY = -1;
    private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;

    private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
    private static final String DAY_OF_WEEK_FORMAT = "EEEEE";

    private final TextPaint mMonthPaint = new TextPaint();
    private final TextPaint mDayOfWeekPaint = new TextPaint();
    private final TextPaint mDayPaint = new TextPaint();
    private final Paint mDaySelectorPaint = new Paint();
    private final Paint mDayHighlightPaint = new Paint();

    private final Calendar mCalendar = Calendar.getInstance();
    private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance();

    private final MonthViewTouchHelper mTouchHelper;

    private final SimpleDateFormat mTitleFormatter;
    private final SimpleDateFormat mDayOfWeekFormatter;
    private final NumberFormat mDayFormatter;

    // Desired dimensions.
    private final int mDesiredMonthHeight;
    private final int mDesiredDayOfWeekHeight;
    private final int mDesiredDayHeight;
    private final int mDesiredCellWidth;
    private final int mDesiredDaySelectorRadius;

    private CharSequence mTitle;

    private int mMonth;
    private int mYear;

    // Dimensions as laid out.
    private int mMonthHeight;
    private int mDayOfWeekHeight;
    private int mDayHeight;
    private int mCellWidth;
    private int mDaySelectorRadius;

    private int mPaddedWidth;
    private int mPaddedHeight;

    /** The day of month for the selected day, or -1 if no day is selected. */
    private int mActivatedDay = -1;

    /**
     * The day of month for today, or -1 if the today is not in the current
     * month.
     */
    private int mToday = DEFAULT_SELECTED_DAY;

    /** The first day of the week (ex. Calendar.SUNDAY). */
    private int mWeekStart = DEFAULT_WEEK_START;

    /** The number of days (ex. 28) in the current month. */
    private int mDaysInMonth;

    /**
     * The day of week (ex. Calendar.SUNDAY) for the first day of the current
     * month.
     */
    private int mDayOfWeekStart;

    /** The day of month for the first (inclusive) enabled day. */
    private int mEnabledDayStart = 1;

    /** The day of month for the last (inclusive) enabled day. */
    private int mEnabledDayEnd = 31;

    /** Optional listener for handling day click actions. */
    private OnDayClickListener mOnDayClickListener;

    private ColorStateList mDayTextColor;

    private int mTouchedItem = -1;

    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 defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        final Resources res = context.getResources();
        mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height);
        mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height);
        mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height);
        mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
        mDesiredDaySelectorRadius = res.getDimensionPixelSize(
                R.dimen.date_picker_day_selector_radius);

        // Set up accessibility components.
        mTouchHelper = new MonthViewTouchHelper(this);
        setAccessibilityDelegate(mTouchHelper);
        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);

        final Locale locale = res.getConfiguration().locale;
        final String titleFormat = DateFormat.getBestDateTimePattern(locale, DEFAULT_TITLE_FORMAT);
        mTitleFormatter = new SimpleDateFormat(titleFormat, locale);
        mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale);
        mDayFormatter = NumberFormat.getIntegerInstance(locale);

        initPaints(res);
    }

    /**
     * Applies the specified text appearance resource to a paint, returning the
     * text color if one is set in the text appearance.
     *
     * @param p the paint to modify
     * @param resId the resource ID of the text appearance
     * @return the text color, if available
     */
    private ColorStateList applyTextAppearance(Paint p, int resId) {
        final TypedArray ta = mContext.obtainStyledAttributes(null,
                R.styleable.TextAppearance, 0, resId);

        final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
        if (fontFamily != null) {
            p.setTypeface(Typeface.create(fontFamily, 0));
        }

        p.setTextSize(ta.getDimensionPixelSize(
                R.styleable.TextAppearance_textSize, (int) p.getTextSize()));

        final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
        if (textColor != null) {
            final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0);
            p.setColor(enabledColor);
        }

        ta.recycle();

        return textColor;
    }

    public int getMonthHeight() {
        return mMonthHeight;
    }

    public int getCellWidth() {
        return mCellWidth;
    }

    public void setMonthTextAppearance(int resId) {
        applyTextAppearance(mMonthPaint, resId);

        invalidate();
    }

    public void setDayOfWeekTextAppearance(int resId) {
        applyTextAppearance(mDayOfWeekPaint, resId);
        invalidate();
    }

    public void setDayTextAppearance(int resId) {
        final ColorStateList textColor = applyTextAppearance(mDayPaint, resId);
        if (textColor != null) {
            mDayTextColor = textColor;
        }

        invalidate();
    }

    public CharSequence getTitle() {
        if (mTitle == null) {
            mTitle = mTitleFormatter.format(mCalendar.getTime());
        }
        return mTitle;
    }

    /**
     * Sets up the text and style properties for painting.
     */
    private void initPaints(Resources res) {
        final String monthTypeface = res.getString(R.string.date_picker_month_typeface);
        final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface);
        final String dayTypeface = res.getString(R.string.date_picker_day_typeface);

        final int monthTextSize = res.getDimensionPixelSize(
                R.dimen.date_picker_month_text_size);
        final int dayOfWeekTextSize = res.getDimensionPixelSize(
                R.dimen.date_picker_day_of_week_text_size);
        final int dayTextSize = res.getDimensionPixelSize(
                R.dimen.date_picker_day_text_size);

        mMonthPaint.setAntiAlias(true);
        mMonthPaint.setTextSize(monthTextSize);
        mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0));
        mMonthPaint.setTextAlign(Align.CENTER);
        mMonthPaint.setStyle(Style.FILL);

        mDayOfWeekPaint.setAntiAlias(true);
        mDayOfWeekPaint.setTextSize(dayOfWeekTextSize);
        mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0));
        mDayOfWeekPaint.setTextAlign(Align.CENTER);
        mDayOfWeekPaint.setStyle(Style.FILL);

        mDaySelectorPaint.setAntiAlias(true);
        mDaySelectorPaint.setStyle(Style.FILL);

        mDayHighlightPaint.setAntiAlias(true);
        mDayHighlightPaint.setStyle(Style.FILL);

        mDayPaint.setAntiAlias(true);
        mDayPaint.setTextSize(dayTextSize);
        mDayPaint.setTypeface(Typeface.create(dayTypeface, 0));
        mDayPaint.setTextAlign(Align.CENTER);
        mDayPaint.setStyle(Style.FILL);
    }

    void setMonthTextColor(ColorStateList monthTextColor) {
        final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
        mMonthPaint.setColor(enabledColor);
        invalidate();
    }

    void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
        final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
        mDayOfWeekPaint.setColor(enabledColor);
        invalidate();
    }

    void setDayTextColor(ColorStateList dayTextColor) {
        mDayTextColor = dayTextColor;
        invalidate();
    }

    void setDaySelectorColor(ColorStateList dayBackgroundColor) {
        final int activatedColor = dayBackgroundColor.getColorForState(
                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
        mDaySelectorPaint.setColor(activatedColor);
        invalidate();
    }

    void setDayHighlightColor(ColorStateList dayHighlightColor) {
        final int pressedColor = dayHighlightColor.getColorForState(
                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0);
        mDayHighlightPaint.setColor(pressedColor);
        invalidate();
    }

    public void setOnDayClickListener(OnDayClickListener listener) {
        mOnDayClickListener = listener;
    }

    @Override
    public boolean dispatchHoverEvent(MotionEvent event) {
        // First right-of-refusal goes the touch exploration helper.
        return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int x = (int) (event.getX() + 0.5f);
        final int y = (int) (event.getY() + 0.5f);

        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                final int touchedItem = getDayAtLocation(x, y);
                if (mTouchedItem != touchedItem) {
                    mTouchedItem = touchedItem;
                    invalidate();
                }
                if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) {
                    // Touch something that's not an item, reject event.
                    return false;
                }
                break;

            case MotionEvent.ACTION_UP:
                final int clickedDay = getDayAtLocation(x, y);
                onDayClicked(clickedDay);
                // Fall through.
            case MotionEvent.ACTION_CANCEL:
                // Reset touched day on stream end.
                mTouchedItem = -1;
                invalidate();
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();
        canvas.translate(paddingLeft, paddingTop);

        drawMonth(canvas);
        drawDaysOfWeek(canvas);
        drawDays(canvas);

        canvas.translate(-paddingLeft, -paddingTop);
    }

    private void drawMonth(Canvas canvas) {
        final float x = mPaddedWidth / 2f;

        // Vertically centered within the month header height.
        final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent();
        final float y = (mMonthHeight - lineHeight) / 2f;

        canvas.drawText(getTitle().toString(), x, y, mMonthPaint);
    }

    private void drawDaysOfWeek(Canvas canvas) {
        final TextPaint p = mDayOfWeekPaint;
        final int headerHeight = mMonthHeight;
        final int rowHeight = mDayOfWeekHeight;
        final int colWidth = mCellWidth;

        // Text is vertically centered within the day of week height.
        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
        final int rowCenter = headerHeight + rowHeight / 2;

        for (int col = 0; col < DAYS_IN_WEEK; col++) {
            final int colCenter = colWidth * col + colWidth / 2;
            final int colCenterRtl;
            if (isLayoutRtl()) {
                colCenterRtl = mPaddedWidth - colCenter;
            } else {
                colCenterRtl = colCenter;
            }

            final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
            final String label = getDayOfWeekLabel(dayOfWeek);
            canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p);
        }
    }

    private String getDayOfWeekLabel(int dayOfWeek) {
        mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
        return mDayOfWeekFormatter.format(mDayOfWeekLabelCalendar.getTime());
    }

    /**
     * Draws the month days.
     */
    private void drawDays(Canvas canvas) {
        final TextPaint p = mDayPaint;
        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
        final int rowHeight = mDayHeight;
        final int colWidth = mCellWidth;

        // Text is vertically centered within the row height.
        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
        int rowCenter = headerHeight + rowHeight / 2;

        for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
            final int colCenter = colWidth * col + colWidth / 2;
            final int colCenterRtl;
            if (isLayoutRtl()) {
                colCenterRtl = mPaddedWidth - colCenter;
            } else {
                colCenterRtl = colCenter;
            }

            int stateMask = 0;

            final boolean isDayEnabled = isDayEnabled(day);
            if (isDayEnabled) {
                stateMask |= StateSet.VIEW_STATE_ENABLED;
            }

            final boolean isDayActivated = mActivatedDay == day;
            if (isDayActivated) {
                stateMask |= StateSet.VIEW_STATE_ACTIVATED;

                // Adjust the circle to be centered on the row.
                canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
            } else if (mTouchedItem == day) {
                stateMask |= StateSet.VIEW_STATE_PRESSED;

                if (isDayEnabled) {
                    // Adjust the circle to be centered on the row.
                    canvas.drawCircle(colCenterRtl, rowCenter,
                            mDaySelectorRadius, mDayHighlightPaint);
                }
            }

            final boolean isDayToday = mToday == day;
            final int dayTextColor;
            if (isDayToday && !isDayActivated) {
                dayTextColor = mDaySelectorPaint.getColor();
            } else {
                final int[] stateSet = StateSet.get(stateMask);
                dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
            }
            p.setColor(dayTextColor);

            canvas.drawText(mDayFormatter.format(day), colCenterRtl, rowCenter - halfLineHeight, p);

            col++;

            if (col == DAYS_IN_WEEK) {
                col = 0;
                rowCenter += rowHeight;
            }
        }
    }

    private boolean isDayEnabled(int day) {
        return day >= mEnabledDayStart && day <= mEnabledDayEnd;
    }

    private boolean isValidDayOfMonth(int day) {
        return day >= 1 && day <= mDaysInMonth;
    }

    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 the selected day.
     *
     * @param dayOfMonth the selected day of the month, or {@code -1} to clear
     *                   the selection
     */
    public void setSelectedDay(int dayOfMonth) {
        mActivatedDay = dayOfMonth;

        // Invalidate cached accessibility information.
        mTouchHelper.invalidateRoot();
        invalidate();
    }

    /**
     * Sets the first day of the week.
     *
     * @param weekStart which day the week should start on, valid values are
     *                  {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
     */
    public void setFirstDayOfWeek(int weekStart) {
        if (isValidDayOfWeek(weekStart)) {
            mWeekStart = weekStart;
        } else {
            mWeekStart = mCalendar.getFirstDayOfWeek();
        }

        // Invalidate cached accessibility information.
        mTouchHelper.invalidateRoot();
        invalidate();
    }

    /**
     * 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, valid values are * {@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) { mActivatedDay = selectedDay; if (isValidMonth(month)) { mMonth = month; } mYear = year; 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(); } // Figure out what day today is. final Calendar today = Calendar.getInstance(); mToday = -1; mDaysInMonth = getDaysInMonth(mMonth, mYear); for (int i = 0; i < mDaysInMonth; i++) { final int day = i + 1; if (sameDay(day, today)) { mToday = day; } } mEnabledDayStart = MathUtils.constrain(enabledDayStart, 1, mDaysInMonth); mEnabledDayEnd = MathUtils.constrain(enabledDayEnd, mEnabledDayStart, mDaysInMonth); // Invalidate the old title. mTitle = null; // 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"); } } private boolean sameDay(int day, Calendar today) { return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH) && day == today.get(Calendar.DAY_OF_MONTH); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int preferredHeight = mDesiredDayHeight * MAX_WEEKS_IN_MONTH + mDesiredDayOfWeekHeight + mDesiredMonthHeight + getPaddingTop() + getPaddingBottom(); final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK + getPaddingStart() + getPaddingEnd(); final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); setMeasuredDimension(resolvedWidth, resolvedHeight); } @Override public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); requestLayout(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (!changed) { return; } // Let's initialize a completely reasonable number of variables. final int w = right - left; final int h = bottom - top; final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int paddingRight = getPaddingRight(); final int paddingBottom = getPaddingBottom(); final int paddedRight = w - paddingRight; final int paddedBottom = h - paddingBottom; final int paddedWidth = paddedRight - paddingLeft; final int paddedHeight = paddedBottom - paddingTop; if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) { return; } mPaddedWidth = paddedWidth; mPaddedHeight = paddedHeight; // We may have been laid out smaller than our preferred size. If so, // scale all dimensions to fit. final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom; final float scaleH = paddedHeight / (float) measuredPaddedHeight; final int monthHeight = (int) (mDesiredMonthHeight * scaleH); final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; mMonthHeight = monthHeight; mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH); mDayHeight = (int) (mDesiredDayHeight * scaleH); mCellWidth = cellWidth; // Compute the largest day selector radius that's still within the clip // bounds and desired selector radius. final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight); final int maxSelectorHeight = mDayHeight / 2 + paddingBottom; mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius, Math.min(maxSelectorWidth, maxSelectorHeight)); // Invalidate cached accessibility information. mTouchHelper.invalidateRoot(); } private int findDayOffset() { final int offset = mDayOfWeekStart - mWeekStart; if (mDayOfWeekStart < mWeekStart) { return offset + DAYS_IN_WEEK; } return offset; } /** * Calculates the day of the month at the specified touch position. Returns * the day of the month or -1 if the position wasn't in a valid day. * * @param x the x position of the touch event * @param y the y position of the touch event * @return the day of the month at (x, y), or -1 if the position wasn't in * a valid day */ private int getDayAtLocation(int x, int y) { final int paddedX = x - getPaddingLeft(); if (paddedX < 0 || paddedX >= mPaddedWidth) { return -1; } final int headerHeight = mMonthHeight + mDayOfWeekHeight; final int paddedY = y - getPaddingTop(); if (paddedY < headerHeight || paddedY >= mPaddedHeight) { return -1; } // Adjust for RTL after applying padding. final int paddedXRtl; if (isLayoutRtl()) { paddedXRtl = mPaddedWidth - paddedX; } else { paddedXRtl = paddedX; } final int row = (paddedY - headerHeight) / mDayHeight; final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth; final int index = col + row * DAYS_IN_WEEK; final int day = index + 1 - findDayOffset(); if (!isValidDayOfMonth(day)) { return -1; } return day; } /** * Calculates the bounds of the specified day. * * @param id the day of the month * @param outBounds the rect to populate with bounds */ private boolean getBoundsForDay(int id, Rect outBounds) { if (!isValidDayOfMonth(id)) { return false; } final int index = id - 1 + findDayOffset(); // Compute left edge, taking into account RTL. final int col = index % DAYS_IN_WEEK; final int colWidth = mCellWidth; final int left; if (isLayoutRtl()) { left = getWidth() - getPaddingRight() - (col + 1) * colWidth; } else { left = getPaddingLeft() + col * colWidth; } // Compute top edge. final int row = index / DAYS_IN_WEEK; final int rowHeight = mDayHeight; final int headerHeight = mMonthHeight + mDayOfWeekHeight; final int top = getPaddingTop() + headerHeight + row * rowHeight; outBounds.set(left, top, left + colWidth, top + rowHeight); return true; } /** * 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 boolean onDayClicked(int day) { if (!isValidDayOfMonth(day) || !isDayEnabled(day)) { return false; } if (mOnDayClickListener != null) { final 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 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); } @Override protected int getVirtualViewAt(float x, float y) { final int day = getDayAtLocation((int) (x + 0.5f), (int) (y + 0.5f)); if (day != -1) { return day; } return ExploreByTouchHelper.INVALID_ID; } @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { for (int day = 1; day <= mDaysInMonth; day++) { virtualViewIds.add(day); } } @Override protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { event.setContentDescription(getDayDescription(virtualViewId)); } @Override protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect); if (!hasBounds) { // The day is invalid, kill the node. mTempRect.setEmpty(); node.setContentDescription(""); node.setBoundsInParent(mTempRect); node.setVisibleToUser(false); return; } node.setText(getDayText(virtualViewId)); node.setContentDescription(getDayDescription(virtualViewId)); node.setBoundsInParent(mTempRect); final boolean isDayEnabled = isDayEnabled(virtualViewId); if (isDayEnabled) { node.addAction(AccessibilityAction.ACTION_CLICK); } node.setEnabled(isDayEnabled); if (virtualViewId == mActivatedDay) { // TODO: This should use activated once that's supported. node.setChecked(true); } } @Override protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: return onDayClicked(virtualViewId); } return false; } /** * Generates a description for a given virtual view. * * @param id the day to generate a description for * @return a description of the virtual view */ private CharSequence getDayDescription(int id) { if (isValidDayOfMonth(id)) { mTempCalendar.set(mYear, mMonth, id); return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis()); } return ""; } /** * Generates displayed text for a given virtual view. * * @param id the day to generate text for * @return the visible text of the virtual view */ private CharSequence getDayText(int id) { if (isValidDayOfMonth(id)) { return mDayFormatter.format(id); } return null; } } /** * Handles callbacks when the user clicks on a time object. */ public interface OnDayClickListener { void onDayClick(SimpleMonthView view, Calendar day); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy