| /* |
| * 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); |
| } |
| } |