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