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