1 /* 2 * Copyright (C) 2015 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.deskclock.stopwatch; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.Paint; 24 import android.graphics.RectF; 25 import android.util.AttributeSet; 26 import android.view.View; 27 28 import com.android.deskclock.R; 29 import com.android.deskclock.Utils; 30 import com.android.deskclock.data.DataModel; 31 import com.android.deskclock.data.Lap; 32 import com.android.deskclock.data.Stopwatch; 33 34 import java.util.List; 35 36 /** 37 * Custom view that draws a reference lap as a circle when one exists. 38 */ 39 public final class StopwatchCircleView extends View { 40 41 /** The size of the dot indicating the user's position within the reference lap. */ 42 private final float mDotRadius; 43 44 /** An amount to subtract from the true radius to account for drawing thicknesses. */ 45 private final float mRadiusOffset; 46 47 /** Used to scale the width of the marker to make it similarly visible on all screens. */ 48 private final float mScreenDensity; 49 50 /** The color indicating the remaining portion of the current lap. */ 51 private final int mRemainderColor; 52 53 /** The color indicating the completed portion of the lap. */ 54 private final int mCompletedColor; 55 56 /** The size of the stroke that paints the lap circle. */ 57 private final float mStrokeSize; 58 59 /** The size of the stroke that paints the marker for the end of the prior lap. */ 60 private final float mMarkerStrokeSize; 61 62 private final Paint mPaint = new Paint(); 63 private final Paint mFill = new Paint(); 64 private final RectF mArcRect = new RectF(); 65 66 @SuppressWarnings("unused") StopwatchCircleView(Context context)67 public StopwatchCircleView(Context context) { 68 this(context, null); 69 } 70 StopwatchCircleView(Context context, AttributeSet attrs)71 public StopwatchCircleView(Context context, AttributeSet attrs) { 72 super(context, attrs); 73 74 final Resources resources = context.getResources(); 75 final float dotDiameter = resources.getDimension(R.dimen.circletimer_dot_size); 76 77 mDotRadius = dotDiameter / 2f; 78 mScreenDensity = resources.getDisplayMetrics().density; 79 mStrokeSize = resources.getDimension(R.dimen.circletimer_circle_size); 80 mMarkerStrokeSize = resources.getDimension(R.dimen.circletimer_marker_size); 81 mRadiusOffset = Utils.calculateRadiusOffset(mStrokeSize, dotDiameter, mMarkerStrokeSize); 82 83 mRemainderColor = resources.getColor(R.color.clock_white); 84 mCompletedColor = Utils.obtainStyledColor(context, R.attr.colorAccent, Color.RED); 85 86 mPaint.setAntiAlias(true); 87 mPaint.setStyle(Paint.Style.STROKE); 88 89 mFill.setAntiAlias(true); 90 mFill.setColor(mCompletedColor); 91 mFill.setStyle(Paint.Style.FILL); 92 } 93 94 /** 95 * Start the animation if it is not currently running. 96 */ update()97 void update() { 98 postInvalidateOnAnimation(); 99 } 100 101 @Override onDraw(Canvas canvas)102 public void onDraw(Canvas canvas) { 103 // Compute the size and location of the circle to be drawn. 104 final int xCenter = getWidth() / 2; 105 final int yCenter = getHeight() / 2; 106 final float radius = Math.min(xCenter, yCenter) - mRadiusOffset; 107 108 // Reset old painting state. 109 mPaint.setColor(mRemainderColor); 110 mPaint.setStrokeWidth(mStrokeSize); 111 112 final List<Lap> laps = getLaps(); 113 114 // If a reference lap does not exist or should not be drawn, draw a simple white circle. 115 if (laps.isEmpty() || !DataModel.getDataModel().canAddMoreLaps()) { 116 // Draw a complete white circle; no red arc required. 117 canvas.drawCircle(xCenter, yCenter, radius, mPaint); 118 119 // No need to continue animating the plain white circle. 120 return; 121 } 122 123 // The first lap is the reference lap to which all future laps are compared. 124 final Stopwatch stopwatch = getStopwatch(); 125 final int lapCount = laps.size(); 126 final Lap firstLap = laps.get(lapCount - 1); 127 final Lap priorLap = laps.get(0); 128 final long firstLapTime = firstLap.getLapTime(); 129 final long currentLapTime = stopwatch.getTotalTime() - priorLap.getAccumulatedTime(); 130 131 // Draw a combination of red and white arcs to create a circle. 132 mArcRect.top = yCenter - radius; 133 mArcRect.bottom = yCenter + radius; 134 mArcRect.left = xCenter - radius; 135 mArcRect.right = xCenter + radius; 136 final float redPercent = (float) currentLapTime / (float) firstLapTime; 137 final float whitePercent = 1 - (redPercent > 1 ? 1 : redPercent); 138 139 // Draw a white arc to indicate the amount of reference lap that remains. 140 canvas.drawArc(mArcRect, 270 + (1 - whitePercent) * 360, whitePercent * 360, false, mPaint); 141 142 // Draw a red arc to indicate the amount of reference lap completed. 143 mPaint.setColor(mCompletedColor); 144 canvas.drawArc(mArcRect, 270, redPercent * 360 , false, mPaint); 145 146 // Starting on lap 2, a marker can be drawn indicating where the prior lap ended. 147 if (lapCount > 1) { 148 mPaint.setColor(mRemainderColor); 149 mPaint.setStrokeWidth(mMarkerStrokeSize); 150 final float markerAngle = (float) priorLap.getLapTime() / (float) firstLapTime * 360; 151 final float startAngle = 270 + markerAngle; 152 final float sweepAngle = mScreenDensity * (float) (360 / (radius * Math.PI)); 153 canvas.drawArc(mArcRect, startAngle, sweepAngle, false, mPaint); 154 } 155 156 // Draw a red dot to indicate current position relative to reference lap. 157 final float dotAngleDegrees = 270 + redPercent * 360; 158 final double dotAngleRadians = Math.toRadians(dotAngleDegrees); 159 final float dotX = xCenter + (float) (radius * Math.cos(dotAngleRadians)); 160 final float dotY = yCenter + (float) (radius * Math.sin(dotAngleRadians)); 161 canvas.drawCircle(dotX, dotY, mDotRadius, mFill); 162 163 // If the stopwatch is not running it does not require continuous updates. 164 if (stopwatch.isRunning()) { 165 postInvalidateOnAnimation(); 166 } 167 } 168 getStopwatch()169 private Stopwatch getStopwatch() { 170 return DataModel.getDataModel().getStopwatch(); 171 } 172 getLaps()173 private List<Lap> getLaps() { 174 return DataModel.getDataModel().getLaps(); 175 } 176 }