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