Home | History | Annotate | Download | only in stopwatch
      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")
     67     public StopwatchCircleView(Context context) {
     68         this(context, null);
     69     }
     70 
     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      */
     97     void update() {
     98         postInvalidateOnAnimation();
     99     }
    100 
    101     @Override
    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 
    169     private Stopwatch getStopwatch() {
    170         return DataModel.getDataModel().getStopwatch();
    171     }
    172 
    173     private List<Lap> getLaps() {
    174         return DataModel.getDataModel().getLaps();
    175     }
    176 }