package com.sage42.android.view.ui;
import java.math.BigDecimal;
import java.math.RoundingMode;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.sage42.android.view.R;
/**
* Copyright (C) 2013- Sage 42 App Sdn Bhd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Corey Scott (corey.scott@sage42.com)
*
*/
public class CircularProgressBar extends View
{
private static final int DEFAULT_MAX_VALUE = 100;
private static final float ADJUST_FOR_12_OCLOCK = 270f;
// properties for the background circle
private final Paint mBgPaint;
// properties for the progress circle
private final Paint mProgressPaint;
// text properties for the countdown text
private boolean mShowText;
private final Paint mTextPaint;
// maximum number of points in the circle default is 100
private int mMax;
// current progress between 0 and mMax
private int mProgress;
// diameter (in dp) of the circle
private float mDiameter;
// margin between circle and edges (default is 4dp)
// NOTE: you will need to include some margin to account for the stroke width, so min padding is strokeWidth/2
private int mLayoutMargin;
// area to draw the progress arc
private RectF mArcBounds;
// height taken to draw text with the current settings
private Rect mTextBounds;
public CircularProgressBar(final Context context, final AttributeSet attrs)
{
this(context, attrs, -1);
}
public CircularProgressBar(final Context context, final AttributeSet attrs, final int defStyle)
{
super(context, attrs, defStyle);
// extract params (if provided)
final TypedArray args = context.obtainStyledAttributes(attrs, R.styleable.circularProgressBar);
final float defaultDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 64, this.getResources()
.getDisplayMetrics());
final float defaultStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, this.getResources()
.getDisplayMetrics());
final float defaultMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, this.getResources()
.getDisplayMetrics());
final float defaultTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, this.getResources()
.getDisplayMetrics());
try
{
final int bgColor = args.getColor(R.styleable.circularProgressBar_bgColor, R.color.black);
final int bgStrokeWidth = args.getDimensionPixelSize(R.styleable.circularProgressBar_bgStrokeWidth,
(int) defaultStrokeWidth);
final int progressColor = args.getColor(R.styleable.circularProgressBar_progressColor, R.color.white);
final int progressStrokeWidth = args.getDimensionPixelSize(
R.styleable.circularProgressBar_progressStrokeWidth, (int) defaultStrokeWidth);
this.mShowText = args.getBoolean(R.styleable.circularProgressBar_showText, false);
final int textSize = args.getDimensionPixelSize(R.styleable.circularProgressBar_android_textSize,
(int) defaultTextSize);
final int textColor = args.getInt(R.styleable.circularProgressBar_android_textColor, R.color.white);
this.mLayoutMargin = args.getDimensionPixelSize(R.styleable.circularProgressBar_android_layout_margin,
(int) defaultMargin);
this.mMax = args.getInt(R.styleable.circularProgressBar_max, DEFAULT_MAX_VALUE);
this.mDiameter = args.getDimension(R.styleable.circularProgressBar_diameter, defaultDiameter);
// create paint settings based on supplied args
this.mBgPaint = new Paint();
this.mBgPaint.setColor(bgColor);
this.mBgPaint.setStyle(Style.STROKE);
this.mBgPaint.setAntiAlias(true);
this.mBgPaint.setStrokeWidth(bgStrokeWidth);
this.mProgressPaint = new Paint();
this.mProgressPaint.setColor(progressColor);
this.mProgressPaint.setStyle(Style.STROKE);
this.mProgressPaint.setAntiAlias(true);
this.mProgressPaint.setStrokeWidth(progressStrokeWidth);
this.mTextPaint = new Paint();
this.mTextPaint.setColor(textColor);
this.mTextPaint.setAntiAlias(true);
this.mTextPaint.setStyle(Style.STROKE);
this.mTextPaint.setTextAlign(Align.CENTER);
this.mTextPaint.setTextSize(textSize);
}
finally
{
args.recycle();
}
}
@Override
protected void onDraw(final Canvas canvas)
{
if (this.mArcBounds == null)
{
// set view bounds for arc drawing
this.mArcBounds = new RectF(this.mLayoutMargin, this.mLayoutMargin, this.mLayoutMargin + this.mDiameter,
this.mLayoutMargin + this.mDiameter);
}
// draw bg circle in the center
final float radius = this.mDiameter / 2;
final float center = radius + this.mLayoutMargin;
canvas.drawCircle(center, center, radius, this.mBgPaint);
// draw any progress over the top
// why is this BigDecimal crap even needed? java why?
final BigDecimal percentage = BigDecimal.valueOf(this.mProgress).divide(BigDecimal.valueOf(this.mMax), 4,
RoundingMode.HALF_DOWN);
final BigDecimal sweepAngle = percentage.multiply(BigDecimal.valueOf(360));
// bounds are same as the bg circle, so diameter width and height moved in by margin
canvas.drawArc(this.mArcBounds, CircularProgressBar.ADJUST_FOR_12_OCLOCK, sweepAngle.floatValue(), false,
this.mProgressPaint);
if (this.mShowText)
{
if (this.mTextBounds == null)
{
// Reference: http://stackoverflow.com/questions/3654321/measuring-text-height-to-be-drawn-on-canvas-android
// answer #2
this.mTextBounds = new Rect();
this.mTextPaint.getTextBounds("0", 0, 1, this.mTextBounds); //$NON-NLS-1$
}
// draw text in the center
canvas.drawText(String.valueOf(this.mProgress), center, center + (this.mTextBounds.height() >> 1),
this.mTextPaint);
}
}
public void setProgress(final int progress)
{
this.mProgress = progress;
// force redraw
this.invalidate();
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
{
// size will always be diameter + margin on add sides
final int size = (int) this.mDiameter + (this.mLayoutMargin * 2);
this.setMeasuredDimension(size, size);
}
public void setMax(final int max)
{
this.mMax = max;
}
public void setBgColor(final int bgColor)
{
this.mBgPaint.setColor(bgColor);
}
public void setBgStrokeWidth(final int bgStrokeWidth)
{
this.mBgPaint.setStrokeWidth(bgStrokeWidth);
}
public void setProgressColor(final int progressColor)
{
this.mProgressPaint.setColor(progressColor);
}
public void setProgressStrokeWidth(final int progressStrokeWidth)
{
this.mProgressPaint.setStrokeWidth(progressStrokeWidth);
}
public void setShowText(final boolean showText)
{
this.mShowText = showText;
}
public void setTextSize(final int textSize)
{
this.mTextPaint.setTextSize(textSize);
}
public void setTextColor(final int textColor)
{
this.mTextPaint.setColor(textColor);
}
public void setDiameter(final float diameter)
{
this.mDiameter = diameter;
}
}