SimpleWeekView.java revision d6c480146a3dbf2a18e1680d9078db5261ff9fe1
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 com.android.calendar.month;
18
19import com.android.calendar.R;
20import com.android.calendar.Utils;
21
22import android.content.Context;
23import android.content.res.Resources;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.Paint.Align;
27import android.graphics.Paint.Style;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.text.format.Time;
31import android.view.View;
32
33import java.security.InvalidParameterException;
34import java.util.HashMap;
35
36/**
37 * <p>
38 * This is a dynamic view for drawing a single week. It can be configured to
39 * display the week number, start the week on a given day, or show a reduced
40 * number of days. It is intended for use as a single view within a ListView.
41 * See {@link SimpleWeeksAdapter} for usage.
42 * </p>
43 */
44public class SimpleWeekView extends View {
45    private static final String TAG = "MonthView";
46
47    /**
48     * These params can be passed into the view to control how it appears.
49     * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
50     * values are unlikely to fit most layouts correctly.
51     */
52    /**
53     * This sets the height of this week in pixels
54     */
55    public static final String VIEW_PARAMS_HEIGHT = "height";
56    /**
57     * This specifies the position (or weeks since the epoch) of this week,
58     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
59     */
60    public static final String VIEW_PARAMS_WEEK = "week";
61    /**
62     * This sets one of the days in this view as selected {@link Time#SUNDAY}
63     * through {@link Time#SATURDAY}.
64     */
65    public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
66    /**
67     * Which day the week should start on. {@link Time#SUNDAY} through
68     * {@link Time#SATURDAY}.
69     */
70    public static final String VIEW_PARAMS_WEEK_START = "week_start";
71    /**
72     * How many days to display at a time. Days will be displayed starting with
73     * {@link #mWeekStart}.
74     */
75    public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
76    /**
77     * Which month is currently in focus, as defined by {@link Time#month}
78     * [0-11].
79     */
80    public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
81    /**
82     * If this month should display week numbers. false if 0, true otherwise.
83     */
84    public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
85
86    protected static int DEFAULT_HEIGHT = 32;
87    protected static int MIN_HEIGHT = 10;
88    protected static final int DEFAULT_SELECTED_DAY = -1;
89    protected static final int DEFAULT_WEEK_START = Time.SUNDAY;
90    protected static final int DEFAULT_NUM_DAYS = 7;
91    protected static final int DEFAULT_SHOW_WK_NUM = 0;
92    protected static final int DEFAULT_FOCUS_MONTH = -1;
93
94    protected static int DAY_SEPARATOR_WIDTH = 1;
95
96    protected static int MINI_DAY_NUMBER_TEXT_SIZE = 14;
97    protected static int MINI_WK_NUMBER_TEXT_SIZE = 12;
98    protected static int MINI_TODAY_NUMBER_TEXT_SIZE = 18;
99    protected static int MINI_TODAY_OUTLINE_WIDTH = 2;
100    protected static int WEEK_NUM_MARGIN_BOTTOM = 4;
101
102    // used for scaling to the device density
103    protected static float mScale = 0;
104
105    // affects the padding on the sides of this view
106    protected int mPadding = 0;
107
108    protected Rect r = new Rect();
109    protected Paint p = new Paint();
110    protected Paint mMonthNumPaint;
111    protected Drawable mSelectedDayLine;
112
113    // Cache the number strings so we don't have to recompute them each time
114    protected String[] mDayNumbers;
115    // Quick lookup for checking which days are in the focus month
116    protected boolean[] mFocusDay;
117    // The Julian day of the first day displayed by this item
118    protected int mFirstJulianDay = -1;
119    // The month of the first day in this week
120    protected int mFirstMonth = -1;
121    // The month of the last day in this week
122    protected int mLastMonth = -1;
123    // The position of this week, equivalent to weeks since the week of Jan 1st,
124    // 1970
125    protected int mWeek = -1;
126    // Quick reference to the width of this view, matches parent
127    protected int mWidth;
128    // The height this view should draw at in pixels, set by height param
129    protected int mHeight = DEFAULT_HEIGHT;
130    // Whether the week number should be shown
131    protected boolean mShowWeekNum = false;
132    // If this view contains the selected day
133    protected boolean mHasSelectedDay = false;
134    // If this view contains the today
135    protected boolean mHasToday = false;
136    // Which day is selected [0-6] or -1 if no day is selected
137    protected int mSelectedDay = DEFAULT_SELECTED_DAY;
138    // Which day is today [0-6] or -1 if no day is today
139    protected int mToday = DEFAULT_SELECTED_DAY;
140    // Which day of the week to start on [0-6]
141    protected int mWeekStart = DEFAULT_WEEK_START;
142    // How many days to display
143    protected int mNumDays = DEFAULT_NUM_DAYS;
144    // The number of days + a spot for week number if it is displayed
145    protected int mNumCells = mNumDays;
146    // The left edge of the selected day
147    protected int mSelectedLeft = -1;
148    // The right edge of the selected day
149    protected int mSelectedRight = -1;
150    // The timezone to display times/dates in (used for determining when Today
151    // is)
152    protected String mTimeZone = Time.getCurrentTimezone();
153
154    protected int mBGColor;
155    protected int mSelectedWeekBGColor;
156    protected int mFocusMonthColor;
157    protected int mOtherMonthColor;
158    protected int mDaySeparatorColor;
159    protected int mTodayOutlineColor;
160    protected int mWeekNumColor;
161
162    public SimpleWeekView(Context context) {
163        super(context);
164
165        Resources res = context.getResources();
166
167        mBGColor = res.getColor(R.color.month_bgcolor);
168        mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor);
169        mFocusMonthColor = res.getColor(R.color.month_mini_day_number);
170        mOtherMonthColor = res.getColor(R.color.month_other_month_day_number);
171        mDaySeparatorColor = res.getColor(R.color.month_grid_lines);
172        mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color);
173        mWeekNumColor = res.getColor(R.color.month_week_num_color);
174        mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light);
175
176        if (mScale == 0) {
177            mScale = context.getResources().getDisplayMetrics().density;
178            if (mScale != 1) {
179                DEFAULT_HEIGHT *= mScale;
180                MIN_HEIGHT *= mScale;
181                MINI_DAY_NUMBER_TEXT_SIZE *= mScale;
182                MINI_TODAY_NUMBER_TEXT_SIZE *= mScale;
183                MINI_TODAY_OUTLINE_WIDTH *= mScale;
184                WEEK_NUM_MARGIN_BOTTOM *= mScale;
185                DAY_SEPARATOR_WIDTH *= mScale;
186                MINI_WK_NUMBER_TEXT_SIZE *= mScale;
187            }
188        }
189
190        // Sets up any standard paints that will be used
191        initView();
192    }
193
194    /**
195     * Sets all the parameters for displaying this week. The only required
196     * parameter is the week number. Other parameters have a default value and
197     * will only update if a new value is included, except for focus month,
198     * which will always default to no focus month if no value is passed in. See
199     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
200     *
201     * @param params A map of the new parameters, see
202     *            {@link #VIEW_PARAMS_HEIGHT}
203     * @param tz The time zone this view should reference times in
204     */
205    public void setWeekParams(HashMap<String, Integer> params, String tz) {
206        if (!params.containsKey(VIEW_PARAMS_WEEK)) {
207            throw new InvalidParameterException("You must specify the week number for this view");
208        }
209        setTag(params);
210        mTimeZone = tz;
211        // We keep the current value for any params not present
212        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
213            mHeight = params.get(VIEW_PARAMS_HEIGHT);
214            if (mHeight < MIN_HEIGHT) {
215                mHeight = MIN_HEIGHT;
216            }
217        }
218        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
219            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
220        }
221        mHasSelectedDay = mSelectedDay != -1;
222        if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) {
223            mNumDays = params.get(VIEW_PARAMS_NUM_DAYS);
224        }
225        if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) {
226            if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) {
227                mShowWeekNum = true;
228            } else {
229                mShowWeekNum = false;
230            }
231        }
232        mNumCells = mShowWeekNum ? mNumDays + 1 : mNumDays;
233
234        // Allocate space for caching the day numbers and focus values
235        mDayNumbers = new String[mNumCells];
236        mFocusDay = new boolean[mNumCells];
237        mWeek = params.get(VIEW_PARAMS_WEEK);
238        int julianMonday = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek);
239        Time time = new Time(tz);
240        time.setJulianDay(julianMonday);
241
242        // If we're showing the week number calculate it based on Monday
243        int i = 0;
244        if (mShowWeekNum) {
245            mDayNumbers[0] = Integer.toString(time.getWeekNumber());
246            i++;
247        }
248
249        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
250            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
251        }
252
253        // Now adjust our starting day based on the start day of the week
254        // If the week is set to start on a Saturday the first week will be
255        // Dec 27th 1969 -Jan 2nd, 1970
256        if (time.weekDay != mWeekStart) {
257            int diff = time.weekDay - mWeekStart;
258            if (diff < 0) {
259                diff += 7;
260            }
261            time.monthDay -= diff;
262            time.normalize(true);
263        }
264
265        mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff);
266        mFirstMonth = time.month;
267
268        // Figure out what day today is
269        Time today = new Time(tz);
270        today.setToNow();
271        mHasToday = false;
272        mToday = -1;
273
274        int focusMonth = params.containsKey(VIEW_PARAMS_FOCUS_MONTH) ? params.get(
275                VIEW_PARAMS_FOCUS_MONTH)
276                : DEFAULT_FOCUS_MONTH;
277
278        for (; i < mNumCells; i++) {
279            if (time.monthDay == 1) {
280                mFirstMonth = time.month;
281            }
282            if (time.month == focusMonth) {
283                mFocusDay[i] = true;
284            } else {
285                mFocusDay[i] = false;
286            }
287            if (time.year == today.year && time.yearDay == today.yearDay) {
288                mHasToday = true;
289                mToday = i;
290            }
291            mDayNumbers[i] = Integer.toString(time.monthDay++);
292            time.normalize(true);
293        }
294        // We do one extra add at the end of the loop, if that pushed us to a
295        // new month undo it
296        if (time.monthDay == 1) {
297            time.monthDay--;
298            time.normalize(true);
299        }
300        mLastMonth = time.month;
301
302        updateSelectionPositions();
303    }
304
305    /**
306     * Sets up the text and style properties for painting. Override this if you
307     * want to use a different paint.
308     */
309    protected void initView() {
310        p.setFakeBoldText(false);
311        p.setAntiAlias(true);
312        p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
313        p.setStyle(Style.FILL);
314
315        mMonthNumPaint = new Paint();
316        mMonthNumPaint.setFakeBoldText(true);
317        mMonthNumPaint.setAntiAlias(true);
318        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
319        mMonthNumPaint.setColor(mFocusMonthColor);
320        mMonthNumPaint.setStyle(Style.FILL);
321        mMonthNumPaint.setTextAlign(Align.CENTER);
322    }
323
324    /**
325     * Returns the month of the first day in this week
326     *
327     * @return The month the first day of this view is in
328     */
329    public int getFirstMonth() {
330        return mFirstMonth;
331    }
332
333    /**
334     * Returns the month of the last day in this week
335     *
336     * @return The month the last day of this view is in
337     */
338    public int getLastMonth() {
339        return mLastMonth;
340    }
341
342    /**
343     * Returns the julian day of the first day in this view.
344     *
345     * @return The julian day of the first day in the view.
346     */
347    public int getFirstJulianDay() {
348        return mFirstJulianDay;
349    }
350
351    /**
352     * Calculates the day that the given x position is in, accounting for week
353     * number. Returns a Time referencing that day or null if
354     *
355     * @param x The x position of the touch event
356     * @return A time object for the tapped day or null if the position wasn't
357     *         in a day
358     */
359    public Time getDayFromLocation(float x) {
360        int dayStart = mShowWeekNum ? (mWidth - mPadding * 2) / mNumCells + mPadding : mPadding;
361        if (x < dayStart || x > mWidth - mPadding) {
362            return null;
363        }
364        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
365        int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
366        int day = mFirstJulianDay + dayPosition;
367
368        Time time = new Time(mTimeZone);
369        if (mWeek == 0) {
370            // This week is weird...
371            if (day < Time.EPOCH_JULIAN_DAY) {
372                day++;
373            } else if (day == Time.EPOCH_JULIAN_DAY) {
374                time.set(1, 0, 1970);
375                time.normalize(true);
376                return time;
377            }
378        }
379
380        time.setJulianDay(day);
381        return time;
382    }
383
384    @Override
385    protected void onDraw(Canvas canvas) {
386        drawBackground(canvas);
387        drawWeekNums(canvas);
388        drawDaySeparators(canvas);
389    }
390
391    /**
392     * This draws the selection highlight if a day is selected in this week.
393     * Override this method if you wish to have a different background drawn.
394     *
395     * @param canvas The canvas to draw on
396     */
397    protected void drawBackground(Canvas canvas) {
398        if (mHasSelectedDay) {
399            p.setColor(mSelectedWeekBGColor);
400            p.setStyle(Style.FILL);
401        } else {
402            return;
403        }
404        r.top = 1;
405        r.bottom = mHeight - 1;
406        r.left = mPadding;
407        r.right = mSelectedLeft;
408        canvas.drawRect(r, p);
409        r.left = mSelectedRight;
410        r.right = mWidth - mPadding;
411        canvas.drawRect(r, p);
412    }
413
414    /**
415     * Draws the week and month day numbers for this week. Override this method
416     * if you need different placement.
417     *
418     * @param canvas The canvas to draw on
419     */
420    protected void drawWeekNums(Canvas canvas) {
421        int y = ((mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH;
422        int nDays = mNumCells;
423
424        int i = 0;
425        int divisor = 2 * nDays;
426        if (mShowWeekNum) {
427            p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE);
428            p.setStyle(Style.FILL);
429            p.setTextAlign(Align.CENTER);
430            p.setAntiAlias(true);
431            p.setColor(mWeekNumColor);
432            int x = (mWidth - mPadding * 2) / divisor + mPadding;
433            canvas.drawText(mDayNumbers[0], x, y, p);
434            i++;
435        }
436
437        boolean isFocusMonth = mFocusDay[i];
438        mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
439        mMonthNumPaint.setFakeBoldText(false);
440        for (; i < nDays; i++) {
441            if (mFocusDay[i] != isFocusMonth) {
442                isFocusMonth = mFocusDay[i];
443                mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
444            }
445            if (mHasToday && mToday == i) {
446                mMonthNumPaint.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE);
447                mMonthNumPaint.setFakeBoldText(true);
448            }
449            int x = (2 * i + 1) * (mWidth - mPadding * 2) / (divisor) + mPadding;
450            canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
451            if (mHasToday && mToday == i) {
452                mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
453                mMonthNumPaint.setFakeBoldText(false);
454            }
455        }
456    }
457
458    /**
459     * Draws a horizontal line for separating the weeks. Override this method if
460     * you want custom separators.
461     *
462     * @param canvas The canvas to draw on
463     */
464    protected void drawDaySeparators(Canvas canvas) {
465        if (mHasSelectedDay) {
466            r.top = 1;
467            r.bottom = mHeight - 1;
468            r.left = mSelectedLeft + 1;
469            r.right = mSelectedRight - 1;
470            p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH);
471            p.setStyle(Style.STROKE);
472            p.setColor(mTodayOutlineColor);
473            canvas.drawRect(r, p);
474        }
475        if (mShowWeekNum) {
476            p.setColor(mDaySeparatorColor);
477            p.setStrokeWidth(DAY_SEPARATOR_WIDTH);
478
479            int x = (mWidth - mPadding * 2) / mNumCells + mPadding;
480            canvas.drawLine(x, 0, x, mHeight, p);
481        }
482    }
483
484    @Override
485    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
486        mWidth = w;
487        updateSelectionPositions();
488    }
489
490    /**
491     * This calculates the positions for the selected day lines.
492     */
493    protected void updateSelectionPositions() {
494        if (mHasSelectedDay) {
495            int selectedPosition = mSelectedDay - mWeekStart;
496            if (selectedPosition < 0) {
497                selectedPosition += 7;
498            }
499            if (mShowWeekNum) {
500                selectedPosition++;
501            }
502            mSelectedLeft = selectedPosition * (mWidth - mPadding * 2) / mNumCells
503                    + mPadding;
504            mSelectedRight = (selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells
505                    + mPadding;
506        }
507    }
508
509    @Override
510    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
511        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
512    }
513}