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