/**
*
*/
package com.marshalchen.common.ui;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import com.marshalchen.common.R;
/**
* The Class HoloCircularProgressBar.
*
* @author Pascal.Welsch
* @since 05.03.2013
*
* @version 1.1 (12.10.2013)
*/
public class HoloCircularProgressBar extends View {
/**
* The Constant TAG.
*/
private static final String TAG = HoloCircularProgressBar.class.getSimpleName();
/**
* used to save the super state on configuration change
*/
private static final String INSTANCE_STATE_SAVEDSTATE = "saved_state";
/**
* used to save the progress on configuration changes
*/
private static final String INSTANCE_STATE_PROGRESS = "progress";
/**
* used to save the marker progress on configuration changes
*/
private static final String INSTANCE_STATE_MARKER_PROGRESS = "marker_progress";
/**
* used to save the background color of the progress
*/
private static final String INSTANCE_STATE_PROGRESS_BACKGROUND_COLOR = "progress_background_color";
/**
* used to save the color of the progress
*/
private static final String INSTANCE_STATE_PROGRESS_COLOR = "progress_color";
/**
* true if not all properties are set. then the view isn't drawn and there
* are no errors in the LayoutEditor
*/
private boolean mIsInitializing = true;
/**
* the paint for the background.
*/
private Paint mBackgroundColorPaint = new Paint();
/**
* The stroke width used to paint the circle.
*/
private int mCircleStrokeWidth = 10;
/**
* The pointer width (in pixels).
*/
private int mThumbRadius = 20;
/**
* The rectangle enclosing the circle.
*/
private final RectF mCircleBounds = new RectF();
/**
* Radius of the circle
*
* <p>
* Note: (Re)calculated in {@link #onMeasure(int, int)}.
* </p>
*/
private float mRadius;
/**
* the color of the progress.
*/
private int mProgressColor;
/**
* paint for the progress.
*/
private Paint mProgressColorPaint;
/**
* The color of the progress background.
*/
private int mProgressBackgroundColor;
/**
* The current progress.
*/
private float mProgress = 0.3f;
/**
* The Thumb color paint.
*/
private Paint mThumbColorPaint = new Paint();
/**
* The Marker progress.
*/
private float mMarkerProgress = 0.0f;
/**
* The Marker color paint.
*/
private Paint mMarkerColorPaint;
/**
* flag if the marker should be visible
*/
private boolean mIsMarkerEnabled = false;
/**
* The gravity of the view. Where should the Circle be drawn within the
* given bounds
*
* {@link #computeInsets(int, int)}
*/
private final int mGravity;
/**
* The Horizontal inset calcualted in {@link #computeInsets(int, int)}
* depends on {@link #mGravity}.
*/
private int mHorizontalInset = 0;
/**
* The Vertical inset calcualted in {@link #computeInsets(int, int)} depends
* on {@link #mGravity}..
*/
private int mVerticalInset = 0;
/**
* The Translation offset x which gives us the ability to use our own
* coordinates system.
*/
private float mTranslationOffsetX;
/**
* The Translation offset y which gives us the ability to use our own
* coordinates system.
*/
private float mTranslationOffsetY;
/**
* The Thumb pos x.
*
* Care. the position is not the position of the rotated thumb. The position
* is only calculated in {@link #onMeasure(int, int)}
*/
private float mThumbPosX;
/**
* The Thumb pos y.
*
* Care. the position is not the position of the rotated thumb. The position
* is only calculated in {@link #onMeasure(int, int)}
*/
private float mThumbPosY;
/**
* the overdraw is true if the progress is over 1.0.
*
*/
private boolean mOverrdraw = false;
/**
* the rect for the thumb square
*/
private final RectF mSquareRect = new RectF();
/**
* indicates if the thumb is visible
*/
private boolean mIsThumbEnabled = true;
/**
* Instantiates a new holo circular progress bar.
*
* @param context
* the context
*/
public HoloCircularProgressBar(final Context context) {
this(context, null);
}
/**
* Instantiates a new holo circular progress bar.
*
* @param context
* the context
* @param attrs
* the attrs
*/
public HoloCircularProgressBar(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.circularProgressBarStyle);
}
/**
* Instantiates a new holo circular progress bar.
*
* @param context
* the context
* @param attrs
* the attrs
* @param defStyle
* the def style
*/
public HoloCircularProgressBar(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
// load the styled attributes and set their properties
final TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.HoloCircularProgressBar,
defStyle, 0);
setProgressColor(attributes.getColor(R.styleable.HoloCircularProgressBar_progress_color, Color.CYAN));
setProgressBackgroundColor(attributes.getColor(R.styleable.HoloCircularProgressBar_progress_background_color,
Color.MAGENTA));
setProgress(attributes.getFloat(R.styleable.HoloCircularProgressBar_progress, 0.0f));
setMarkerProgress(attributes.getFloat(R.styleable.HoloCircularProgressBar_marker_progress, 0.0f));
setWheelSize((int) attributes.getDimension(R.styleable.HoloCircularProgressBar_stroke_width, 10));
mIsThumbEnabled = attributes.getBoolean(R.styleable.HoloCircularProgressBar_thumb_visible, true);
mIsMarkerEnabled = attributes.getBoolean(R.styleable.HoloCircularProgressBar_marker_visible, true);
mGravity = attributes.getInt(R.styleable.HoloCircularProgressBar_android_gravity, Gravity.CENTER);
attributes.recycle();
mThumbRadius = mCircleStrokeWidth * 2;
updateBackgroundColor();
updateMarkerColor();
updateProgressColor();
// the view has now all properties and can be drawn
mIsInitializing = false;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(final Canvas canvas) {
// All of our positions are using our internal coordinate system.
// Instead of translating
// them we let Canvas do the work for us.
canvas.translate(mTranslationOffsetX, mTranslationOffsetY);
final float progressRotation = getCurrentRotation();
// draw the background
if (!mOverrdraw) {
canvas.drawArc(mCircleBounds, 270, -(360 - progressRotation), false, mBackgroundColorPaint);
}
// draw the progress or a full circle if overdraw is true
canvas.drawArc(mCircleBounds, 270, mOverrdraw ? 360 : progressRotation, false, mProgressColorPaint);
// draw the marker at the correct rotated position
if (mIsMarkerEnabled) {
final float markerRotation = getMarkerRotation();
canvas.save();
canvas.rotate(markerRotation - 90);
canvas.drawLine((float) (mThumbPosX + mThumbRadius / 2 * 1.4), mThumbPosY,
(float) (mThumbPosX - mThumbRadius / 2 * 1.4), mThumbPosY, mMarkerColorPaint);
canvas.restore();
}
if (isThumbEnabled()) {
// draw the thumb square at the correct rotated position
canvas.save();
canvas.rotate(progressRotation - 90);
// rotate the square by 45 degrees
canvas.rotate(45, mThumbPosX, mThumbPosY);
mSquareRect.left = mThumbPosX - mThumbRadius / 3;
mSquareRect.right = mThumbPosX + mThumbRadius / 3;
mSquareRect.top = mThumbPosY - mThumbRadius / 3;
mSquareRect.bottom = mThumbPosY + mThumbRadius / 3;
canvas.drawRect(mSquareRect, mThumbColorPaint);
canvas.restore();
}
}
/*
* (non-Javadoc)
*
* @see android.view.View#onMeasure(int, int)
*/
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
final int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int min = Math.min(width, height);
setMeasuredDimension(min, height);
final float halfWidth = min * 0.5f;
mRadius = halfWidth - mThumbRadius;
mCircleBounds.set(-mRadius, -mRadius, mRadius, mRadius);
mThumbPosX = (float) (mRadius * Math.cos(0));
mThumbPosY = (float) (mRadius * Math.sin(0));
computeInsets(width - min, height - min);
mTranslationOffsetX = halfWidth + mHorizontalInset;
mTranslationOffsetY = halfWidth + mVerticalInset;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
*/
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (state instanceof Bundle) {
final Bundle bundle = (Bundle) state;
setProgress(bundle.getFloat(INSTANCE_STATE_PROGRESS));
setMarkerProgress(bundle.getFloat(INSTANCE_STATE_MARKER_PROGRESS));
final int progressColor = bundle.getInt(INSTANCE_STATE_PROGRESS_COLOR);
if (progressColor != mProgressColor) {
mProgressColor = progressColor;
updateProgressColor();
}
final int progressBackgroundColor = bundle.getInt(INSTANCE_STATE_PROGRESS_BACKGROUND_COLOR);
if (progressBackgroundColor != mProgressBackgroundColor) {
mProgressBackgroundColor = progressBackgroundColor;
updateBackgroundColor();
}
super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE_SAVEDSTATE));
return;
}
super.onRestoreInstanceState(state);
}
/*
* (non-Javadoc)
*
* @see android.view.View#onSaveInstanceState()
*/
@Override
protected Parcelable onSaveInstanceState() {
final Bundle bundle = new Bundle();
bundle.putParcelable(INSTANCE_STATE_SAVEDSTATE, super.onSaveInstanceState());
bundle.putFloat(INSTANCE_STATE_PROGRESS, mProgress);
bundle.putFloat(INSTANCE_STATE_MARKER_PROGRESS, mMarkerProgress);
bundle.putInt(INSTANCE_STATE_PROGRESS_COLOR, mProgressColor);
bundle.putInt(INSTANCE_STATE_PROGRESS_BACKGROUND_COLOR, mProgressBackgroundColor);
return bundle;
}
/**
* Compute insets.
*
* <pre>
* ______________________
* |_________dx/2_________|
* |......| /'''''\|......|
* |-dx/2-|| View ||-dx/2-|
* |______| \_____/|______|
* |________ dx/2_________|
* </pre>
*
* @param dx
* the dx the horizontal unfilled space
* @param dy
* the dy the horizontal unfilled space
*/
@SuppressLint("NewApi")
private void computeInsets(final int dx, final int dy) {
final int layoutDirection;
int absoluteGravity = mGravity;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
layoutDirection = getLayoutDirection();
absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
}
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
mHorizontalInset = 0;
break;
case Gravity.RIGHT:
mHorizontalInset = dx;
break;
case Gravity.CENTER_HORIZONTAL:
default:
mHorizontalInset = dx / 2;
break;
}
switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
mVerticalInset = 0;
break;
case Gravity.BOTTOM:
mVerticalInset = dy;
break;
case Gravity.CENTER_VERTICAL:
default:
mVerticalInset = dy / 2;
break;
}
}
/**
* Gets the current rotation.
*
* @return the current rotation
*/
private float getCurrentRotation() {
return 360 * mProgress;
}
/**
* Gets the marker rotation.
*
* @return the marker rotation
*/
private float getMarkerRotation() {
return 360 * mMarkerProgress;
}
/**
* Sets the wheel size.
*
* @param dimension
* the new wheel size
*/
private void setWheelSize(final int dimension) {
mCircleStrokeWidth = dimension;
}
/**
* updates the paint of the background
*/
private void updateBackgroundColor() {
mBackgroundColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackgroundColorPaint.setColor(mProgressBackgroundColor);
mBackgroundColorPaint.setStyle(Paint.Style.STROKE);
mBackgroundColorPaint.setStrokeWidth(mCircleStrokeWidth);
invalidate();
}
/**
* updates the paint of the marker
*/
private void updateMarkerColor() {
mMarkerColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMarkerColorPaint.setColor(mProgressBackgroundColor);
mMarkerColorPaint.setStyle(Paint.Style.STROKE);
mMarkerColorPaint.setStrokeWidth(mCircleStrokeWidth / 2);
invalidate();
}
/**
* updates the paint of the progress and the thumb to give them a new visual
* style
*/
private void updateProgressColor() {
mProgressColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mProgressColorPaint.setColor(mProgressColor);
mProgressColorPaint.setStyle(Paint.Style.STROKE);
mProgressColorPaint.setStrokeWidth(mCircleStrokeWidth);
mThumbColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mThumbColorPaint.setColor(mProgressColor);
mThumbColorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mThumbColorPaint.setStrokeWidth(mCircleStrokeWidth);
invalidate();
}
/**
* similar to {@link }
*
* @return
*/
public float getMarkerProgress() {
return mMarkerProgress;
}
/**
* gives the current progress of the ProgressBar. Value between 0..1 if you
* set the progress to >1 you'll get progress % 1 as return value
*
* @return the progress
*/
public float getProgress() {
return mProgress;
}
/**
* Gets the progress color.
*
* @return the progress color
*/
public int getProgressColor() {
return mProgressColor;
}
/**
*
* @return true if the marker is visible
*/
public boolean isMarkerEnabled() {
return mIsMarkerEnabled;
}
/**
*
* @return true if the marker is visible
*/
public boolean isThumbEnabled() {
return mIsThumbEnabled;
}
/**
* Sets the marker enabled.
*
* @param enabled
* the new marker enabled
*/
public void setMarkerEnabled(final boolean enabled) {
mIsMarkerEnabled = enabled;
}
/**
* Sets the marker progress.
*
* @param progress
* the new marker progress
*/
public void setMarkerProgress(final float progress) {
mIsMarkerEnabled = true;
mMarkerProgress = progress;
}
/**
* Sets the progress.
*
* @param progress
* the new progress
*/
public void setProgress(final float progress) {
if (progress == mProgress) {
return;
}
if (progress == 1) {
mOverrdraw = false;
mProgress = 1;
} else {
if (progress >= 1) {
mOverrdraw = true;
} else {
mOverrdraw = false;
}
mProgress = progress % 1.0f;
}
if (!mIsInitializing) {
invalidate();
}
}
/**
* Sets the progress background color.
*
* @param color
* the new progress background color
*/
public void setProgressBackgroundColor(final int color) {
mProgressBackgroundColor = color;
updateMarkerColor();
updateBackgroundColor();
}
/**
* Sets the progress color.
*
* @param color
* the new progress color
*/
public void setProgressColor(final int color) {
mProgressColor = color;
updateProgressColor();
}
public void setThumbEnabled(final boolean enabled) {
mIsThumbEnabled = enabled;
}
}