/* * Copyright (C) 2007 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 com.android.globaltime; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.text.format.DateUtils; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; /** * A class that draws an analog clock face with information about the current * time in a given city. */ public class Clock { static final int MILLISECONDS_PER_MINUTE = 60 * 1000; static final int MILLISECONDS_PER_HOUR = 60 * 60 * 1000; private City mCity = null; private long mCitySwitchTime; private long mTime; private float mColorRed = 1.0f; private float mColorGreen = 1.0f; private float mColorBlue = 1.0f; private long mOldOffset; private Interpolator mClockHandInterpolator = new AccelerateDecelerateInterpolator(); public Clock() { // Empty constructor } /** * Adds a line to the given Path. The line extends from * radius r0 to radius r1 about the center point (cx, cy), * at an angle given by pos. * * @param path the Path to draw to * @param radius the radius of the outer rim of the clock * @param pos the angle, with 0 and 1 at 12:00 * @param cx the X coordinate of the clock center * @param cy the Y coordinate of the clock center * @param r0 the starting radius for the line * @param r1 the ending radius for the line */ private static void drawLine(Path path, float radius, float pos, float cx, float cy, float r0, float r1) { float theta = pos * Shape.TWO_PI - Shape.PI_OVER_TWO; float dx = (float) Math.cos(theta); float dy = (float) Math.sin(theta); float p0x = cx + dx * r0; float p0y = cy + dy * r0; float p1x = cx + dx * r1; float p1y = cy + dy * r1; float ox = (p1y - p0y); float oy = -(p1x - p0x); float norm = (radius / 2.0f) / (float) Math.sqrt(ox * ox + oy * oy); ox *= norm; oy *= norm; path.moveTo(p0x - ox, p0y - oy); path.lineTo(p1x - ox, p1y - oy); path.lineTo(p1x + ox, p1y + oy); path.lineTo(p0x + ox, p0y + oy); path.close(); } /** * Adds a vertical arrow to the given Path. * * @param path the Path to draw to */ private static void drawVArrow(Path path, float cx, float cy, float width, float height) { path.moveTo(cx - width / 2.0f, cy); path.lineTo(cx, cy + height); path.lineTo(cx + width / 2.0f, cy); path.close(); } /** * Adds a horizontal arrow to the given Path. * * @param path the Path to draw to */ private static void drawHArrow(Path path, float cx, float cy, float width, float height) { path.moveTo(cx, cy - height / 2.0f); path.lineTo(cx + width, cy); path.lineTo(cx, cy + height / 2.0f); path.close(); } /** * Returns an offset in milliseconds to be subtracted from the current time * in order to obtain an smooth interpolation between the previously * displayed time and the current time. */ private long getOffset(float lerp) { long doffset = (long) (mCity.getOffset() * (float) MILLISECONDS_PER_HOUR - mOldOffset); int sign; if (doffset < 0) { doffset = -doffset; sign = -1; } else { sign = 1; } while (doffset > 12L * MILLISECONDS_PER_HOUR) { doffset -= 12L * MILLISECONDS_PER_HOUR; } if (doffset > 6L * MILLISECONDS_PER_HOUR) { doffset = 12L * MILLISECONDS_PER_HOUR - doffset; sign = -sign; } // Interpolate doffset towards 0 doffset = (long)((1.0f - lerp)*doffset); // Keep the same seconds count long dh = doffset / (MILLISECONDS_PER_HOUR); doffset -= dh * MILLISECONDS_PER_HOUR; long dm = doffset / MILLISECONDS_PER_MINUTE; doffset = sign * (60 * dh + dm) * MILLISECONDS_PER_MINUTE; return doffset; } /** * Set the city to be displayed. setCity(null) resets things so the clock * hand animation won't occur next time. */ public void setCity(City city) { if (mCity != city) { if (mCity != null) { mOldOffset = (long) (mCity.getOffset() * (float) MILLISECONDS_PER_HOUR); } else if (city != null) { mOldOffset = (long) (city.getOffset() * (float) MILLISECONDS_PER_HOUR); } else { mOldOffset = 0L; // this will never be used } this.mCitySwitchTime = System.currentTimeMillis(); this.mCity = city; } } public void setTime(long time) { this.mTime = time; } /** * Draws the clock face. * * @param canvas the Canvas to draw to * @param cx the X coordinate of the clock center * @param cy the Y coordinate of the clock center * @param radius the radius of the clock face * @param alpha the translucency of the clock face * @param textAlpha the translucency of the text * @param showCityName if true, display the city name * @param showTime if true, display the time digitally * @param showUpArrow if true, display an up arrow * @param showDownArrow if true, display a down arrow * @param showLeftRightArrows if true, display left and right arrows * @param prefixChars number of characters of the city name to draw in bold */ public void drawClock(Canvas canvas, float cx, float cy, float radius, float alpha, float textAlpha, boolean showCityName, boolean showTime, boolean showUpArrow, boolean showDownArrow, boolean showLeftRightArrows, int prefixChars) { Paint paint = new Paint(); paint.setAntiAlias(true); int iradius = (int)radius; TimeZone tz = mCity.getTimeZone(); // Compute an interpolated time to animate between the previously // displayed time and the current time float lerp = Math.min(1.0f, (System.currentTimeMillis() - mCitySwitchTime) / 500.0f); lerp = mClockHandInterpolator.getInterpolation(lerp); long doffset = lerp < 1.0f ? getOffset(lerp) : 0L; // Determine the interpolated time for the given time zone Calendar cal = Calendar.getInstance(tz); cal.setTimeInMillis(mTime - doffset); int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); int second = cal.get(Calendar.SECOND); int milli = cal.get(Calendar.MILLISECOND); float offset = tz.getRawOffset() / (float) MILLISECONDS_PER_HOUR; float daylightOffset = tz.inDaylightTime(new Date(mTime)) ? tz.getDSTSavings() / (float) MILLISECONDS_PER_HOUR : 0.0f; float absOffset = offset < 0 ? -offset : offset; int offsetH = (int) absOffset; int offsetM = (int) (60.0f * (absOffset - offsetH)); hour %= 12; // Get the city name and digital time strings String cityName = mCity.getName(); cal.setTimeInMillis(mTime); String time = DateUtils.timeString(cal.getTimeInMillis()) + " " + DateUtils.getDayOfWeekString(cal.get(Calendar.DAY_OF_WEEK), DateUtils.LENGTH_SHORT) + " " + " (UTC" + (offset >= 0 ? "+" : "-") + offsetH + (offsetM == 0 ? "" : ":" + offsetM) + (daylightOffset == 0 ? "" : "+" + daylightOffset) + ")"; float th = paint.getTextSize(); float tw; // Set the text color paint.setARGB((int) (textAlpha * 255.0f), (int) (mColorRed * 255.0f), (int) (mColorGreen * 255.0f), (int) (mColorBlue * 255.0f)); tw = paint.measureText(cityName); if (showCityName) { // Increment prefixChars to include any spaces for (int i = 0; i < prefixChars; i++) { if (cityName.charAt(i) == ' ') { ++prefixChars; } } // Draw the city name canvas.drawText(cityName, cx - tw / 2, cy - radius - th, paint); // Overstrike the first 'prefixChars' characters canvas.drawText(cityName.substring(0, prefixChars), cx - tw / 2 + 1, cy - radius - th, paint); } tw = paint.measureText(time); if (showTime) { canvas.drawText(time, cx - tw / 2, cy + radius + th + 5, paint); } paint.setARGB((int)(alpha * 255.0f), (int)(mColorRed * 255.0f), (int)(mColorGreen * 255.0f), (int)(mColorBlue * 255.0f)); paint.setStyle(Paint.Style.FILL); canvas.drawOval(new RectF(cx - 2, cy - 2, cx + 2, cy + 2), paint); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(radius * 0.12f); canvas.drawOval(new RectF(cx - iradius, cy - iradius, cx + iradius, cy + iradius), paint); float r0 = radius * 0.1f; float r1 = radius * 0.4f; float r2 = radius * 0.6f; float r3 = radius * 0.65f; float r4 = radius * 0.7f; float r5 = radius * 0.9f; Path path = new Path(); float ss = second + milli / 1000.0f; float mm = minute + ss / 60.0f; float hh = hour + mm / 60.0f; // Tics for the hours for (int i = 0; i < 12; i++) { drawLine(path, radius * 0.12f, i / 12.0f, cx, cy, r4, r5); } // Hour hand drawLine(path, radius * 0.12f, hh / 12.0f, cx, cy, r0, r1); // Minute hand drawLine(path, radius * 0.12f, mm / 60.0f, cx, cy, r0, r2); // Second hand drawLine(path, radius * 0.036f, ss / 60.0f, cx, cy, r0, r3); if (showUpArrow) { drawVArrow(path, cx + radius * 1.13f, cy - radius, radius * 0.15f, -radius * 0.1f); } if (showDownArrow) { drawVArrow(path, cx + radius * 1.13f, cy + radius, radius * 0.15f, radius * 0.1f); } if (showLeftRightArrows) { drawHArrow(path, cx - radius * 1.3f, cy, -radius * 0.1f, radius * 0.15f); drawHArrow(path, cx + radius * 1.3f, cy, radius * 0.1f, radius * 0.15f); } paint.setStyle(Paint.Style.FILL); canvas.drawPath(path, paint); } }