16d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/*
26d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Copyright (C) 2015 The Android Open Source Project
36d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *
46d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Licensed under the Apache License, Version 2.0 (the "License");
56d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * you may not use this file except in compliance with the License.
66d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * You may obtain a copy of the License at
76d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *
86d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *      http://www.apache.org/licenses/LICENSE-2.0
96d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *
106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Unless required by applicable law or agreed to in writing, software
116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * distributed under the License is distributed on an "AS IS" BASIS,
126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * See the License for the specific language governing permissions and
146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * limitations under the License.
156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */
166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpackage com.android.deskclock.timer;
186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.content.Context;
206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.content.res.Resources;
216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.graphics.Canvas;
226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.graphics.Color;
236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.graphics.Paint;
246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.graphics.RectF;
256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.util.AttributeSet;
266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.view.View;
276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.R;
296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.Utils;
306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.data.Timer;
316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/**
336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Custom view that draws timer progress as a circle.
346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */
356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpublic final class TimerCircleView extends View {
366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** The size of the dot indicating the progress through the timer. */
386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final float mDotRadius;
396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** An amount to subtract from the true radius to account for drawing thicknesses. */
416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final float mRadiusOffset;
426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** The color indicating the remaining portion of the timer. */
446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final int mRemainderColor;
456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** The color indicating the completed portion of the timer. */
476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final int mCompletedColor;
486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** The size of the stroke that paints the timer circle. */
506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final float mStrokeSize;
516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final Paint mPaint = new Paint();
536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final Paint mFill = new Paint();
546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final RectF mArcRect = new RectF();
556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private Timer mTimer;
576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @SuppressWarnings("unused")
596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public TimerCircleView(Context context) {
606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        this(context, null);
616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public TimerCircleView(Context context, AttributeSet attrs) {
646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        super(context, attrs);
656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final Resources resources = context.getResources();
676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final float dotDiameter = resources.getDimension(R.dimen.circletimer_dot_size);
686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
696d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mDotRadius = dotDiameter / 2f;
706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mStrokeSize = resources.getDimension(R.dimen.circletimer_circle_size);
716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mRadiusOffset = Utils.calculateRadiusOffset(mStrokeSize, dotDiameter, 0);
726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
736d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mRemainderColor = resources.getColor(R.color.clock_white);
746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mCompletedColor = Utils.obtainStyledColor(context, R.attr.colorAccent, Color.RED);
756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mPaint.setAntiAlias(true);
776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mPaint.setStyle(Paint.Style.STROKE);
786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mFill.setAntiAlias(true);
806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mFill.setColor(mCompletedColor);
816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mFill.setStyle(Paint.Style.FILL);
826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    void update(Timer timer) {
856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (mTimer != timer) {
866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mTimer = timer;
876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            postInvalidateOnAnimation();
886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @Override
926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void onDraw(Canvas canvas) {
936d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (mTimer == null) {
946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            return;
956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Compute the size and location of the circle to be drawn.
986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final int xCenter = getWidth() / 2;
996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final int yCenter = getHeight() / 2;
1006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final float radius = Math.min(xCenter, yCenter) - mRadiusOffset;
1016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Reset old painting state.
1036d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mPaint.setColor(mRemainderColor);
1046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mPaint.setStrokeWidth(mStrokeSize);
1056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // If the timer is reset, draw a simple white circle.
1076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final float redPercent;
1086d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (mTimer.isReset()) {
1096d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Draw a complete white circle; no red arc required.
1106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            canvas.drawCircle(xCenter, yCenter, radius, mPaint);
1116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Red percent is 0 since no timer progress has been made.
1136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            redPercent = 0;
1146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        } else if (mTimer.isExpired()) {
1156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mPaint.setColor(mCompletedColor);
1166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Draw a complete white circle; no red arc required.
1186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            canvas.drawCircle(xCenter, yCenter, radius, mPaint);
1196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Red percent is 1 since the timer has expired.
1216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            redPercent = 1;
1226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        } else {
1236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Draw a combination of red and white arcs to create a circle.
1246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mArcRect.top = yCenter - radius;
1256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mArcRect.bottom = yCenter + radius;
1266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mArcRect.left = xCenter - radius;
1276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mArcRect.right = xCenter + radius;
1286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            redPercent = Math.min(1, (float) mTimer.getElapsedTime() / (float) mTimer.getTotalLength());
1296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            final float whitePercent = 1 - redPercent;
1306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Draw a white arc to indicate the amount of timer that remains.
1326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            canvas.drawArc(mArcRect, 270, whitePercent * 360, false, mPaint);
1336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Draw a red arc to indicate the amount of timer completed.
1356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mPaint.setColor(mCompletedColor);
1366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            canvas.drawArc(mArcRect, 270, -redPercent * 360 , false, mPaint);
1376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
1386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Draw a red dot to indicate current progress through the timer.
1406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final float dotAngleDegrees = 270 - redPercent * 360;
1416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final double dotAngleRadians = Math.toRadians(dotAngleDegrees);
1426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final float dotX = xCenter + (float) (radius * Math.cos(dotAngleRadians));
1436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final float dotY = yCenter + (float) (radius * Math.sin(dotAngleRadians));
1446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        canvas.drawCircle(dotX, dotY, mDotRadius, mFill);
1456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (mTimer.isRunning()) {
1476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            postInvalidateOnAnimation();
1486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
1496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux}
151