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 }