1 /* 2 * Copyright (C) 2006 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 android.widget; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.content.BroadcastReceiver; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.graphics.Canvas; 26 import android.graphics.drawable.Drawable; 27 import android.os.Handler; 28 import android.text.format.Time; 29 import android.util.AttributeSet; 30 import android.view.View; 31 import android.widget.RemoteViews.RemoteView; 32 33 import java.util.TimeZone; 34 35 /** 36 * This widget display an analogic clock with two hands for hours and 37 * minutes. 38 */ 39 @RemoteView 40 public class AnalogClock extends View { 41 private Time mCalendar; 42 43 private Drawable mHourHand; 44 private Drawable mMinuteHand; 45 private Drawable mDial; 46 47 private int mDialWidth; 48 private int mDialHeight; 49 50 private boolean mAttached; 51 52 private final Handler mHandler = new Handler(); 53 private float mMinutes; 54 private float mHour; 55 private boolean mChanged; 56 AnalogClock(Context context)57 public AnalogClock(Context context) { 58 this(context, null); 59 } 60 AnalogClock(Context context, AttributeSet attrs)61 public AnalogClock(Context context, AttributeSet attrs) { 62 this(context, attrs, 0); 63 } 64 AnalogClock(Context context, AttributeSet attrs, int defStyle)65 public AnalogClock(Context context, AttributeSet attrs, 66 int defStyle) { 67 super(context, attrs, defStyle); 68 Resources r = mContext.getResources(); 69 TypedArray a = 70 context.obtainStyledAttributes( 71 attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0); 72 73 mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial); 74 if (mDial == null) { 75 mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial); 76 } 77 78 mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour); 79 if (mHourHand == null) { 80 mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour); 81 } 82 83 mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute); 84 if (mMinuteHand == null) { 85 mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute); 86 } 87 88 mCalendar = new Time(); 89 90 mDialWidth = mDial.getIntrinsicWidth(); 91 mDialHeight = mDial.getIntrinsicHeight(); 92 } 93 94 @Override onAttachedToWindow()95 protected void onAttachedToWindow() { 96 super.onAttachedToWindow(); 97 98 if (!mAttached) { 99 mAttached = true; 100 IntentFilter filter = new IntentFilter(); 101 102 filter.addAction(Intent.ACTION_TIME_TICK); 103 filter.addAction(Intent.ACTION_TIME_CHANGED); 104 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 105 106 getContext().registerReceiver(mIntentReceiver, filter, null, mHandler); 107 } 108 109 // NOTE: It's safe to do these after registering the receiver since the receiver always runs 110 // in the main thread, therefore the receiver can't run before this method returns. 111 112 // The time zone may have changed while the receiver wasn't registered, so update the Time 113 mCalendar = new Time(); 114 115 // Make sure we update to the current time 116 onTimeChanged(); 117 } 118 119 @Override onDetachedFromWindow()120 protected void onDetachedFromWindow() { 121 super.onDetachedFromWindow(); 122 if (mAttached) { 123 getContext().unregisterReceiver(mIntentReceiver); 124 mAttached = false; 125 } 126 } 127 128 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)129 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 130 131 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 132 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 133 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 134 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 135 136 float hScale = 1.0f; 137 float vScale = 1.0f; 138 139 if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) { 140 hScale = (float) widthSize / (float) mDialWidth; 141 } 142 143 if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) { 144 vScale = (float )heightSize / (float) mDialHeight; 145 } 146 147 float scale = Math.min(hScale, vScale); 148 149 setMeasuredDimension(resolveSize((int) (mDialWidth * scale), widthMeasureSpec), 150 resolveSize((int) (mDialHeight * scale), heightMeasureSpec)); 151 } 152 153 @Override onSizeChanged(int w, int h, int oldw, int oldh)154 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 155 super.onSizeChanged(w, h, oldw, oldh); 156 mChanged = true; 157 } 158 159 @Override onDraw(Canvas canvas)160 protected void onDraw(Canvas canvas) { 161 super.onDraw(canvas); 162 163 boolean changed = mChanged; 164 if (changed) { 165 mChanged = false; 166 } 167 168 int availableWidth = mRight - mLeft; 169 int availableHeight = mBottom - mTop; 170 171 int x = availableWidth / 2; 172 int y = availableHeight / 2; 173 174 final Drawable dial = mDial; 175 int w = dial.getIntrinsicWidth(); 176 int h = dial.getIntrinsicHeight(); 177 178 boolean scaled = false; 179 180 if (availableWidth < w || availableHeight < h) { 181 scaled = true; 182 float scale = Math.min((float) availableWidth / (float) w, 183 (float) availableHeight / (float) h); 184 canvas.save(); 185 canvas.scale(scale, scale, x, y); 186 } 187 188 if (changed) { 189 dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)); 190 } 191 dial.draw(canvas); 192 193 canvas.save(); 194 canvas.rotate(mHour / 12.0f * 360.0f, x, y); 195 final Drawable hourHand = mHourHand; 196 if (changed) { 197 w = hourHand.getIntrinsicWidth(); 198 h = hourHand.getIntrinsicHeight(); 199 hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)); 200 } 201 hourHand.draw(canvas); 202 canvas.restore(); 203 204 canvas.save(); 205 canvas.rotate(mMinutes / 60.0f * 360.0f, x, y); 206 207 final Drawable minuteHand = mMinuteHand; 208 if (changed) { 209 w = minuteHand.getIntrinsicWidth(); 210 h = minuteHand.getIntrinsicHeight(); 211 minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)); 212 } 213 minuteHand.draw(canvas); 214 canvas.restore(); 215 216 if (scaled) { 217 canvas.restore(); 218 } 219 } 220 onTimeChanged()221 private void onTimeChanged() { 222 mCalendar.setToNow(); 223 224 int hour = mCalendar.hour; 225 int minute = mCalendar.minute; 226 int second = mCalendar.second; 227 228 mMinutes = minute + second / 60.0f; 229 mHour = hour + mMinutes / 60.0f; 230 mChanged = true; 231 } 232 233 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 234 @Override 235 public void onReceive(Context context, Intent intent) { 236 if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) { 237 String tz = intent.getStringExtra("time-zone"); 238 mCalendar = new Time(TimeZone.getTimeZone(tz).getID()); 239 } 240 241 onTimeChanged(); 242 243 invalidate(); 244 } 245 }; 246 } 247