1 package com.android.deskclock; 2 3 import android.content.Context; 4 import android.content.SharedPreferences; 5 import android.content.res.Configuration; 6 import android.content.res.Resources; 7 import android.graphics.Canvas; 8 import android.graphics.Paint; 9 import android.graphics.RectF; 10 import android.util.AttributeSet; 11 import android.view.View; 12 13 import com.android.deskclock.stopwatch.Stopwatches; 14 15 /** 16 * TODO: Insert description here. (generated by isaackatz) 17 */ 18 public class CircleTimerView extends View { 19 20 21 private int mRedColor; 22 private int mWhiteColor; 23 private long mIntervalTime = 0; 24 private long mIntervalStartTime = -1; 25 private long mMarkerTime = -1; 26 private long mCurrentIntervalTime = 0; 27 private long mAccumulatedTime = 0; 28 private boolean mPaused = false; 29 private boolean mAnimate = false; 30 private static float mCircleXCenterLeftPadding = 0; 31 private static float mStrokeSize = 4; 32 private static float mDiamondStrokeSize = 12; 33 private static float mMarkerStrokeSize = 2; 34 private final Paint mPaint = new Paint(); 35 private final Paint mFill = new Paint(); 36 private final RectF mArcRect = new RectF(); 37 private float mRectHalfWidth = 6f; 38 private Resources mResources; 39 private float mRadiusOffset; // amount to remove from radius to account for markers on circle 40 private float mScreenDensity; 41 42 // Class has 2 modes: 43 // Timer mode - counting down. in this mode the animation is counter-clockwise and stops at 0 44 // Stop watch mode - counting up - in this mode the animation is clockwise and will keep the 45 // animation until stopped. 46 private boolean mTimerMode = false; // default is stop watch view 47 48 public CircleTimerView(Context context) { 49 this(context, null); 50 } 51 52 public CircleTimerView(Context context, AttributeSet attrs) { 53 super(context, attrs); 54 init(context); 55 } 56 57 public void setIntervalTime(long t) { 58 mIntervalTime = t; 59 postInvalidate(); 60 } 61 62 public void setMarkerTime(long t) { 63 mMarkerTime = t; 64 postInvalidate(); 65 } 66 67 public void reset() { 68 mIntervalStartTime = -1; 69 mMarkerTime = -1; 70 postInvalidate(); 71 } 72 public void startIntervalAnimation() { 73 mIntervalStartTime = Utils.getTimeNow(); 74 mAnimate = true; 75 invalidate(); 76 mPaused = false; 77 } 78 public void stopIntervalAnimation() { 79 mAnimate = false; 80 mIntervalStartTime = -1; 81 mAccumulatedTime = 0; 82 } 83 84 public boolean isAnimating() { 85 return (mIntervalStartTime != -1); 86 } 87 88 public void pauseIntervalAnimation() { 89 mAnimate = false; 90 mAccumulatedTime += Utils.getTimeNow() - mIntervalStartTime; 91 mPaused = true; 92 } 93 94 public void abortIntervalAnimation() { 95 mAnimate = false; 96 } 97 98 public void setPassedTime(long time, boolean drawRed) { 99 // The onDraw() method checks if mIntervalStartTime has been set before drawing any red. 100 // Without drawRed, mIntervalStartTime should not be set here at all, and would remain at -1 101 // when the state is reconfigured after exiting and re-entering the application. 102 // If the timer is currently running, this drawRed will not be set, and will have no effect 103 // because mIntervalStartTime will be set when the thread next runs. 104 // When the timer is not running, mIntervalStartTime will not be set upon loading the state, 105 // and no red will be drawn, so drawRed is used to force onDraw() to draw the red portion, 106 // despite the timer not running. 107 mCurrentIntervalTime = mAccumulatedTime = time; 108 if (drawRed) { 109 mIntervalStartTime = Utils.getTimeNow(); 110 } 111 postInvalidate(); 112 } 113 114 115 116 private void init(Context c) { 117 118 mResources = c.getResources(); 119 mCircleXCenterLeftPadding = (mResources.getDimension(R.dimen.timer_circle_width) 120 - mResources.getDimension(R.dimen.timer_circle_diameter)) / 2; 121 mStrokeSize = mResources.getDimension(R.dimen.circletimer_circle_size); 122 mDiamondStrokeSize = mResources.getDimension(R.dimen.circletimer_diamond_size); 123 mMarkerStrokeSize = mResources.getDimension(R.dimen.circletimer_marker_size); 124 mRadiusOffset = Utils.calculateRadiusOffset( 125 mStrokeSize, mDiamondStrokeSize, mMarkerStrokeSize); 126 mPaint.setAntiAlias(true); 127 mPaint.setStyle(Paint.Style.STROKE); 128 mWhiteColor = mResources.getColor(R.color.clock_white); 129 mRedColor = mResources.getColor(R.color.clock_red); 130 mScreenDensity = mResources.getDisplayMetrics().density; 131 mFill.setAntiAlias(true); 132 mFill.setStyle(Paint.Style.FILL); 133 mFill.setColor(mRedColor); 134 mRectHalfWidth = mDiamondStrokeSize / 2f; 135 } 136 137 public void setTimerMode(boolean mode) { 138 mTimerMode = mode; 139 } 140 141 @Override 142 public void onDraw(Canvas canvas) { 143 int xCenter = getWidth() / 2 + 1; 144 int yCenter = getHeight() / 2; 145 146 mPaint.setStrokeWidth(mStrokeSize); 147 float radius = Math.min(xCenter, yCenter) - mRadiusOffset; 148 149 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 150 xCenter = (int) (radius + mRadiusOffset); 151 if (mTimerMode) { 152 xCenter += mCircleXCenterLeftPadding; 153 } 154 } 155 156 if (mIntervalStartTime == -1) { 157 // just draw a complete white circle, no red arc needed 158 mPaint.setColor(mWhiteColor); 159 canvas.drawCircle (xCenter, yCenter, radius, mPaint); 160 if (mTimerMode) { 161 drawRedDiamond(canvas, 0f, xCenter, yCenter, radius); 162 } 163 } else { 164 if (mAnimate) { 165 mCurrentIntervalTime = Utils.getTimeNow() - mIntervalStartTime + mAccumulatedTime; 166 } 167 //draw a combination of red and white arcs to create a circle 168 mArcRect.top = yCenter - radius; 169 mArcRect.bottom = yCenter + radius; 170 mArcRect.left = xCenter - radius; 171 mArcRect.right = xCenter + radius; 172 float redPercent = (float)mCurrentIntervalTime / (float)mIntervalTime; 173 // prevent timer from doing more than one full circle 174 redPercent = (redPercent > 1 && mTimerMode) ? 1 : redPercent; 175 176 float whitePercent = 1 - (redPercent > 1 ? 1 : redPercent); 177 // draw red arc here 178 mPaint.setColor(mRedColor); 179 if (mTimerMode){ 180 canvas.drawArc (mArcRect, 270, - redPercent * 360 , false, mPaint); 181 } else { 182 canvas.drawArc (mArcRect, 270, + redPercent * 360 , false, mPaint); 183 } 184 185 // draw white arc here 186 mPaint.setStrokeWidth(mStrokeSize); 187 mPaint.setColor(mWhiteColor); 188 if (mTimerMode) { 189 canvas.drawArc(mArcRect, 270, + whitePercent * 360, false, mPaint); 190 } else { 191 canvas.drawArc(mArcRect, 270 + (1 - whitePercent) * 360, 192 whitePercent * 360, false, mPaint); 193 } 194 195 if (mMarkerTime != -1 && radius > 0 && mIntervalTime != 0) { 196 mPaint.setStrokeWidth(mMarkerStrokeSize); 197 float angle = (float)(mMarkerTime % mIntervalTime) / (float)mIntervalTime * 360; 198 // draw 2dips thick marker 199 // the formula to draw the marker 1 unit thick is: 200 // 180 / (radius * Math.PI) 201 // after that we have to scale it by the screen density 202 canvas.drawArc (mArcRect, 270 + angle, mScreenDensity * 203 (float) (360 / (radius * Math.PI)) , false, mPaint); 204 } 205 drawRedDiamond(canvas, redPercent, xCenter, yCenter, radius); 206 } 207 if (mAnimate) { 208 invalidate(); 209 } 210 } 211 212 protected void drawRedDiamond( 213 Canvas canvas, float degrees, int xCenter, int yCenter, float radius) { 214 mPaint.setColor(mRedColor); 215 float diamondPercent; 216 if (mTimerMode) { 217 diamondPercent = 270 - degrees * 360; 218 } else { 219 diamondPercent = 270 + degrees * 360; 220 } 221 222 canvas.save(); 223 final double diamondRadians = Math.toRadians(diamondPercent); 224 canvas.translate(xCenter + (float) (radius * Math.cos(diamondRadians)), 225 yCenter + (float) (radius * Math.sin(diamondRadians))); 226 canvas.rotate(diamondPercent + 45f); 227 canvas.drawRect(-mRectHalfWidth, -mRectHalfWidth, mRectHalfWidth, mRectHalfWidth, mFill); 228 canvas.restore(); 229 } 230 231 public static final String PREF_CTV_PAUSED = "_ctv_paused"; 232 public static final String PREF_CTV_INTERVAL = "_ctv_interval"; 233 public static final String PREF_CTV_INTERVAL_START = "_ctv_interval_start"; 234 public static final String PREF_CTV_CURRENT_INTERVAL = "_ctv_current_interval"; 235 public static final String PREF_CTV_ACCUM_TIME = "_ctv_accum_time"; 236 public static final String PREF_CTV_TIMER_MODE = "_ctv_timer_mode"; 237 public static final String PREF_CTV_MARKER_TIME = "_ctv_marker_time"; 238 239 // Since this view is used in multiple places, use the key to save different instances 240 public void writeToSharedPref(SharedPreferences prefs, String key) { 241 SharedPreferences.Editor editor = prefs.edit(); 242 editor.putBoolean (key + PREF_CTV_PAUSED, mPaused); 243 editor.putLong (key + PREF_CTV_INTERVAL, mIntervalTime); 244 editor.putLong (key + PREF_CTV_INTERVAL_START, mIntervalStartTime); 245 editor.putLong (key + PREF_CTV_CURRENT_INTERVAL, mCurrentIntervalTime); 246 editor.putLong (key + PREF_CTV_ACCUM_TIME, mAccumulatedTime); 247 editor.putLong (key + PREF_CTV_MARKER_TIME, mMarkerTime); 248 editor.putBoolean (key + PREF_CTV_TIMER_MODE, mTimerMode); 249 editor.apply(); 250 } 251 252 public void readFromSharedPref(SharedPreferences prefs, String key) { 253 mPaused = prefs.getBoolean(key + PREF_CTV_PAUSED, false); 254 mIntervalTime = prefs.getLong(key + PREF_CTV_INTERVAL, 0); 255 mIntervalStartTime = prefs.getLong(key + PREF_CTV_INTERVAL_START, -1); 256 mCurrentIntervalTime = prefs.getLong(key + PREF_CTV_CURRENT_INTERVAL, 0); 257 mAccumulatedTime = prefs.getLong(key + PREF_CTV_ACCUM_TIME, 0); 258 mMarkerTime = prefs.getLong(key + PREF_CTV_MARKER_TIME, -1); 259 mTimerMode = prefs.getBoolean(key + PREF_CTV_TIMER_MODE, false); 260 mAnimate = (mIntervalStartTime != -1 && !mPaused); 261 } 262 263 public void clearSharedPref(SharedPreferences prefs, String key) { 264 SharedPreferences.Editor editor = prefs.edit(); 265 editor.remove (Stopwatches.PREF_START_TIME); 266 editor.remove (Stopwatches.PREF_ACCUM_TIME); 267 editor.remove (Stopwatches.PREF_STATE); 268 editor.remove (key + PREF_CTV_PAUSED); 269 editor.remove (key + PREF_CTV_INTERVAL); 270 editor.remove (key + PREF_CTV_INTERVAL_START); 271 editor.remove (key + PREF_CTV_CURRENT_INTERVAL); 272 editor.remove (key + PREF_CTV_ACCUM_TIME); 273 editor.remove (key + PREF_CTV_MARKER_TIME); 274 editor.remove (key + PREF_CTV_TIMER_MODE); 275 editor.apply(); 276 } 277 } 278