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.timer;
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.ThemeUtils;
30 import com.android.deskclock.Utils;
31 import com.android.deskclock.data.Timer;
32 
33 /**
34  * Custom view that draws timer progress as a circle.
35  */
36 public final class TimerCircleView extends View {
37 
38     /** The size of the dot indicating the progress through the timer. */
39     private final float mDotRadius;
40 
41     /** An amount to subtract from the true radius to account for drawing thicknesses. */
42     private final float mRadiusOffset;
43 
44     /** The color indicating the remaining portion of the timer. */
45     private final int mRemainderColor;
46 
47     /** The color indicating the completed portion of the timer. */
48     private final int mCompletedColor;
49 
50     /** The size of the stroke that paints the timer circle. */
51     private final float mStrokeSize;
52 
53     private final Paint mPaint = new Paint();
54     private final Paint mFill = new Paint();
55     private final RectF mArcRect = new RectF();
56 
57     private Timer mTimer;
58 
59     @SuppressWarnings("unused")
TimerCircleView(Context context)60     public TimerCircleView(Context context) {
61         this(context, null);
62     }
63 
TimerCircleView(Context context, AttributeSet attrs)64     public TimerCircleView(Context context, AttributeSet attrs) {
65         super(context, attrs);
66 
67         final Resources resources = context.getResources();
68         final float dotDiameter = resources.getDimension(R.dimen.circletimer_dot_size);
69 
70         mDotRadius = dotDiameter / 2f;
71         mStrokeSize = resources.getDimension(R.dimen.circletimer_circle_size);
72         mRadiusOffset = Utils.calculateRadiusOffset(mStrokeSize, dotDiameter, 0);
73 
74         mRemainderColor = Color.WHITE;
75         mCompletedColor = ThemeUtils.resolveColor(context, R.attr.colorAccent);
76 
77         mPaint.setAntiAlias(true);
78         mPaint.setStyle(Paint.Style.STROKE);
79 
80         mFill.setAntiAlias(true);
81         mFill.setColor(mCompletedColor);
82         mFill.setStyle(Paint.Style.FILL);
83     }
84 
update(Timer timer)85     void update(Timer timer) {
86         if (mTimer != timer) {
87             mTimer = timer;
88             postInvalidateOnAnimation();
89         }
90     }
91 
92     @Override
onDraw(Canvas canvas)93     public void onDraw(Canvas canvas) {
94         if (mTimer == null) {
95             return;
96         }
97 
98         // Compute the size and location of the circle to be drawn.
99         final int xCenter = getWidth() / 2;
100         final int yCenter = getHeight() / 2;
101         final float radius = Math.min(xCenter, yCenter) - mRadiusOffset;
102 
103         // Reset old painting state.
104         mPaint.setColor(mRemainderColor);
105         mPaint.setStrokeWidth(mStrokeSize);
106 
107         // If the timer is reset, draw a simple white circle.
108         final float redPercent;
109         if (mTimer.isReset()) {
110             // Draw a complete white circle; no red arc required.
111             canvas.drawCircle(xCenter, yCenter, radius, mPaint);
112 
113             // Red percent is 0 since no timer progress has been made.
114             redPercent = 0;
115         } else if (mTimer.isExpired()) {
116             mPaint.setColor(mCompletedColor);
117 
118             // Draw a complete white circle; no red arc required.
119             canvas.drawCircle(xCenter, yCenter, radius, mPaint);
120 
121             // Red percent is 1 since the timer has expired.
122             redPercent = 1;
123         } else {
124             // Draw a combination of red and white arcs to create a circle.
125             mArcRect.top = yCenter - radius;
126             mArcRect.bottom = yCenter + radius;
127             mArcRect.left = xCenter - radius;
128             mArcRect.right = xCenter + radius;
129             redPercent = Math.min(1, (float) mTimer.getElapsedTime() / (float) mTimer.getTotalLength());
130             final float whitePercent = 1 - redPercent;
131 
132             // Draw a white arc to indicate the amount of timer that remains.
133             canvas.drawArc(mArcRect, 270, whitePercent * 360, false, mPaint);
134 
135             // Draw a red arc to indicate the amount of timer completed.
136             mPaint.setColor(mCompletedColor);
137             canvas.drawArc(mArcRect, 270, -redPercent * 360 , false, mPaint);
138         }
139 
140         // Draw a red dot to indicate current progress through the timer.
141         final float dotAngleDegrees = 270 - redPercent * 360;
142         final double dotAngleRadians = Math.toRadians(dotAngleDegrees);
143         final float dotX = xCenter + (float) (radius * Math.cos(dotAngleRadians));
144         final float dotY = yCenter + (float) (radius * Math.sin(dotAngleRadians));
145         canvas.drawCircle(dotX, dotY, mDotRadius, mFill);
146 
147         if (mTimer.isRunning()) {
148             postInvalidateOnAnimation();
149         }
150     }
151 }
152