1 /* 2 * Copyright (C) 2007 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 17 package com.android.globaltime; 18 19 import java.util.Calendar; 20 import java.util.Date; 21 import java.util.TimeZone; 22 23 import android.graphics.Canvas; 24 import android.graphics.Paint; 25 import android.graphics.Path; 26 import android.graphics.RectF; 27 import android.text.format.DateUtils; 28 import android.view.animation.AccelerateDecelerateInterpolator; 29 import android.view.animation.Interpolator; 30 31 /** 32 * A class that draws an analog clock face with information about the current 33 * time in a given city. 34 */ 35 public class Clock { 36 37 static final int MILLISECONDS_PER_MINUTE = 60 * 1000; 38 static final int MILLISECONDS_PER_HOUR = 60 * 60 * 1000; 39 40 private City mCity = null; 41 private long mCitySwitchTime; 42 private long mTime; 43 44 private float mColorRed = 1.0f; 45 private float mColorGreen = 1.0f; 46 private float mColorBlue = 1.0f; 47 48 private long mOldOffset; 49 50 private Interpolator mClockHandInterpolator = 51 new AccelerateDecelerateInterpolator(); 52 53 public Clock() { 54 // Empty constructor 55 } 56 57 /** 58 * Adds a line to the given Path. The line extends from 59 * radius r0 to radius r1 about the center point (cx, cy), 60 * at an angle given by pos. 61 * 62 * @param path the Path to draw to 63 * @param radius the radius of the outer rim of the clock 64 * @param pos the angle, with 0 and 1 at 12:00 65 * @param cx the X coordinate of the clock center 66 * @param cy the Y coordinate of the clock center 67 * @param r0 the starting radius for the line 68 * @param r1 the ending radius for the line 69 */ 70 private static void drawLine(Path path, 71 float radius, float pos, float cx, float cy, float r0, float r1) { 72 float theta = pos * Shape.TWO_PI - Shape.PI_OVER_TWO; 73 float dx = (float) Math.cos(theta); 74 float dy = (float) Math.sin(theta); 75 float p0x = cx + dx * r0; 76 float p0y = cy + dy * r0; 77 float p1x = cx + dx * r1; 78 float p1y = cy + dy * r1; 79 80 float ox = (p1y - p0y); 81 float oy = -(p1x - p0x); 82 83 float norm = (radius / 2.0f) / (float) Math.sqrt(ox * ox + oy * oy); 84 ox *= norm; 85 oy *= norm; 86 87 path.moveTo(p0x - ox, p0y - oy); 88 path.lineTo(p1x - ox, p1y - oy); 89 path.lineTo(p1x + ox, p1y + oy); 90 path.lineTo(p0x + ox, p0y + oy); 91 path.close(); 92 } 93 94 /** 95 * Adds a vertical arrow to the given Path. 96 * 97 * @param path the Path to draw to 98 */ 99 private static void drawVArrow(Path path, 100 float cx, float cy, float width, float height) { 101 path.moveTo(cx - width / 2.0f, cy); 102 path.lineTo(cx, cy + height); 103 path.lineTo(cx + width / 2.0f, cy); 104 path.close(); 105 } 106 107 /** 108 * Adds a horizontal arrow to the given Path. 109 * 110 * @param path the Path to draw to 111 */ 112 private static void drawHArrow(Path path, 113 float cx, float cy, float width, float height) { 114 path.moveTo(cx, cy - height / 2.0f); 115 path.lineTo(cx + width, cy); 116 path.lineTo(cx, cy + height / 2.0f); 117 path.close(); 118 } 119 120 /** 121 * Returns an offset in milliseconds to be subtracted from the current time 122 * in order to obtain an smooth interpolation between the previously 123 * displayed time and the current time. 124 */ 125 private long getOffset(float lerp) { 126 long doffset = (long) (mCity.getOffset() * 127 (float) MILLISECONDS_PER_HOUR - mOldOffset); 128 int sign; 129 if (doffset < 0) { 130 doffset = -doffset; 131 sign = -1; 132 } else { 133 sign = 1; 134 } 135 136 while (doffset > 12L * MILLISECONDS_PER_HOUR) { 137 doffset -= 12L * MILLISECONDS_PER_HOUR; 138 } 139 if (doffset > 6L * MILLISECONDS_PER_HOUR) { 140 doffset = 12L * MILLISECONDS_PER_HOUR - doffset; 141 sign = -sign; 142 } 143 144 // Interpolate doffset towards 0 145 doffset = (long)((1.0f - lerp)*doffset); 146 147 // Keep the same seconds count 148 long dh = doffset / (MILLISECONDS_PER_HOUR); 149 doffset -= dh * MILLISECONDS_PER_HOUR; 150 long dm = doffset / MILLISECONDS_PER_MINUTE; 151 doffset = sign * (60 * dh + dm) * MILLISECONDS_PER_MINUTE; 152 153 return doffset; 154 } 155 156 /** 157 * Set the city to be displayed. setCity(null) resets things so the clock 158 * hand animation won't occur next time. 159 */ 160 public void setCity(City city) { 161 if (mCity != city) { 162 if (mCity != null) { 163 mOldOffset = 164 (long) (mCity.getOffset() * (float) MILLISECONDS_PER_HOUR); 165 } else if (city != null) { 166 mOldOffset = 167 (long) (city.getOffset() * (float) MILLISECONDS_PER_HOUR); 168 } else { 169 mOldOffset = 0L; // this will never be used 170 } 171 this.mCitySwitchTime = System.currentTimeMillis(); 172 this.mCity = city; 173 } 174 } 175 176 public void setTime(long time) { 177 this.mTime = time; 178 } 179 180 /** 181 * Draws the clock face. 182 * 183 * @param canvas the Canvas to draw to 184 * @param cx the X coordinate of the clock center 185 * @param cy the Y coordinate of the clock center 186 * @param radius the radius of the clock face 187 * @param alpha the translucency of the clock face 188 * @param textAlpha the translucency of the text 189 * @param showCityName if true, display the city name 190 * @param showTime if true, display the time digitally 191 * @param showUpArrow if true, display an up arrow 192 * @param showDownArrow if true, display a down arrow 193 * @param showLeftRightArrows if true, display left and right arrows 194 * @param prefixChars number of characters of the city name to draw in bold 195 */ 196 public void drawClock(Canvas canvas, 197 float cx, float cy, float radius, float alpha, float textAlpha, 198 boolean showCityName, boolean showTime, 199 boolean showUpArrow, boolean showDownArrow, boolean showLeftRightArrows, 200 int prefixChars) { 201 Paint paint = new Paint(); 202 paint.setAntiAlias(true); 203 204 int iradius = (int)radius; 205 206 TimeZone tz = mCity.getTimeZone(); 207 208 // Compute an interpolated time to animate between the previously 209 // displayed time and the current time 210 float lerp = Math.min(1.0f, 211 (System.currentTimeMillis() - mCitySwitchTime) / 500.0f); 212 lerp = mClockHandInterpolator.getInterpolation(lerp); 213 long doffset = lerp < 1.0f ? getOffset(lerp) : 0L; 214 215 // Determine the interpolated time for the given time zone 216 Calendar cal = Calendar.getInstance(tz); 217 cal.setTimeInMillis(mTime - doffset); 218 int hour = cal.get(Calendar.HOUR_OF_DAY); 219 int minute = cal.get(Calendar.MINUTE); 220 int second = cal.get(Calendar.SECOND); 221 int milli = cal.get(Calendar.MILLISECOND); 222 223 float offset = tz.getRawOffset() / (float) MILLISECONDS_PER_HOUR; 224 float daylightOffset = tz.inDaylightTime(new Date(mTime)) ? 225 tz.getDSTSavings() / (float) MILLISECONDS_PER_HOUR : 0.0f; 226 227 float absOffset = offset < 0 ? -offset : offset; 228 int offsetH = (int) absOffset; 229 int offsetM = (int) (60.0f * (absOffset - offsetH)); 230 hour %= 12; 231 232 // Get the city name and digital time strings 233 String cityName = mCity.getName(); 234 cal.setTimeInMillis(mTime); 235 String time = DateUtils.timeString(cal.getTimeInMillis()) + " " + 236 DateUtils.getDayOfWeekString(cal.get(Calendar.DAY_OF_WEEK), 237 DateUtils.LENGTH_SHORT) + " " + 238 " (UTC" + 239 (offset >= 0 ? "+" : "-") + 240 offsetH + 241 (offsetM == 0 ? "" : ":" + offsetM) + 242 (daylightOffset == 0 ? "" : "+" + daylightOffset) + 243 ")"; 244 245 float th = paint.getTextSize(); 246 float tw; 247 248 // Set the text color 249 paint.setARGB((int) (textAlpha * 255.0f), 250 (int) (mColorRed * 255.0f), 251 (int) (mColorGreen * 255.0f), 252 (int) (mColorBlue * 255.0f)); 253 254 tw = paint.measureText(cityName); 255 if (showCityName) { 256 // Increment prefixChars to include any spaces 257 for (int i = 0; i < prefixChars; i++) { 258 if (cityName.charAt(i) == ' ') { 259 ++prefixChars; 260 } 261 } 262 263 // Draw the city name 264 canvas.drawText(cityName, cx - tw / 2, cy - radius - th, paint); 265 // Overstrike the first 'prefixChars' characters 266 canvas.drawText(cityName.substring(0, prefixChars), 267 cx - tw / 2 + 1, cy - radius - th, paint); 268 } 269 tw = paint.measureText(time); 270 if (showTime) { 271 canvas.drawText(time, cx - tw / 2, cy + radius + th + 5, paint); 272 } 273 274 paint.setARGB((int)(alpha * 255.0f), 275 (int)(mColorRed * 255.0f), 276 (int)(mColorGreen * 255.0f), 277 (int)(mColorBlue * 255.0f)); 278 279 paint.setStyle(Paint.Style.FILL); 280 canvas.drawOval(new RectF(cx - 2, cy - 2, cx + 2, cy + 2), paint); 281 282 paint.setStyle(Paint.Style.STROKE); 283 paint.setStrokeWidth(radius * 0.12f); 284 285 canvas.drawOval(new RectF(cx - iradius, cy - iradius, 286 cx + iradius, cy + iradius), 287 paint); 288 289 float r0 = radius * 0.1f; 290 float r1 = radius * 0.4f; 291 float r2 = radius * 0.6f; 292 float r3 = radius * 0.65f; 293 float r4 = radius * 0.7f; 294 float r5 = radius * 0.9f; 295 296 Path path = new Path(); 297 298 float ss = second + milli / 1000.0f; 299 float mm = minute + ss / 60.0f; 300 float hh = hour + mm / 60.0f; 301 302 // Tics for the hours 303 for (int i = 0; i < 12; i++) { 304 drawLine(path, radius * 0.12f, i / 12.0f, cx, cy, r4, r5); 305 } 306 307 // Hour hand 308 drawLine(path, radius * 0.12f, hh / 12.0f, cx, cy, r0, r1); 309 // Minute hand 310 drawLine(path, radius * 0.12f, mm / 60.0f, cx, cy, r0, r2); 311 // Second hand 312 drawLine(path, radius * 0.036f, ss / 60.0f, cx, cy, r0, r3); 313 314 if (showUpArrow) { 315 drawVArrow(path, cx + radius * 1.13f, cy - radius, 316 radius * 0.15f, -radius * 0.1f); 317 } 318 if (showDownArrow) { 319 drawVArrow(path, cx + radius * 1.13f, cy + radius, 320 radius * 0.15f, radius * 0.1f); 321 } 322 if (showLeftRightArrows) { 323 drawHArrow(path, cx - radius * 1.3f, cy, -radius * 0.1f, 324 radius * 0.15f); 325 drawHArrow(path, cx + radius * 1.3f, cy, radius * 0.1f, 326 radius * 0.15f); 327 } 328 329 paint.setStyle(Paint.Style.FILL); 330 canvas.drawPath(path, paint); 331 } 332 } 333