/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Rahat Ahmed
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package mbanje.kurt.fabbutton;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
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.util.AttributeSet;
import android.view.View;
import com.fastbootmobile.encore.app.R;
public class ProgressRingView extends View implements FabUtil.OnFabValueCallback {
private static final String TAG = ProgressRingView.class.getSimpleName();
private Paint mProgressPaint;
private int mSize = 0;
private RectF mBounds;
private float mBoundsPadding = 0.14f;
private int mViewRadius;
private float mRingWithRatio = 0.14f; //of a possible 1f;
private boolean mIndeterminate = false, mAutoStartAnim;
private boolean mWasIndeterminate;
private float mProgress, mMaxProgress, mIndeterminateSweep, mIndeterminateRotateOffset;
private int mRingWidth, mMidRingWidth, mAnimDuration;
private int mProgressColor = Color.BLACK;
// Animation related stuff
private float mStartAngle;
private float mActualProgress;
private ValueAnimator mStartAngleRotate;
private ValueAnimator mProgressAnimation;
private AnimatorSet mIndeterminateAnimator;
private CircleImageView.OnFabViewListener fabViewListener;
public ProgressRingView(Context context) {
super(context);
init(null, 0);
}
public ProgressRingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public ProgressRingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
protected void init(AttributeSet attrs, int defStyle) {
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
mProgress = a.getFloat(R.styleable.CircleImageView_android_progress, 0f);
mProgressColor = a.getColor(R.styleable.CircleImageView_fbb_progressColor, mProgressColor);
mMaxProgress = a.getFloat(R.styleable.CircleImageView_android_max, 100f);
mIndeterminate = a.getBoolean(R.styleable.CircleImageView_android_indeterminate, false);
mAutoStartAnim = a.getBoolean(R.styleable.CircleImageView_fbb_autoStart, true);
mAnimDuration = a.getInteger(R.styleable.CircleImageView_android_indeterminateDuration, 4000);
mRingWithRatio = a.getFloat(R.styleable.CircleImageView_fbb_progressWidthRatio, mRingWithRatio);
a.recycle();
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mProgressPaint.setColor(mProgressColor);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeCap(Paint.Cap.BUTT);
if (mAutoStartAnim) {
startAnimation();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mSize = Math.min(w, h);
mViewRadius = mSize / 2;
setRingWidth(-1, true);
}
public void setRingWithRatio(float ringWithRatio) {
this.mRingWithRatio = ringWithRatio;
}
public void setAutostartanim(boolean autostartanim) {
this.mAutoStartAnim = autostartanim;
}
public void setFabViewListener(CircleImageView.OnFabViewListener fabViewListener) {
this.fabViewListener = fabViewListener;
}
public void setRingWidth(int width, boolean original) {
if (original) {
mRingWidth = Math.round((float) mViewRadius * mRingWithRatio);
} else {
mRingWidth = width;
}
mMidRingWidth = mRingWidth / 2;
mProgressPaint.setStrokeWidth(mRingWidth);
updateBounds();
}
private void updateBounds() {
mBounds = new RectF(mMidRingWidth, mMidRingWidth, mSize - mMidRingWidth, mSize - mMidRingWidth);
}
@Override
protected void onDraw(Canvas canvas) {
// Draw the arc
float sweepAngle = isInEditMode() ? (mProgress / mMaxProgress * 360) : (mActualProgress / mMaxProgress * 360);
if (!mIndeterminate) {
canvas.drawArc(mBounds, mStartAngle, sweepAngle, false, mProgressPaint);
} else {
canvas.drawArc(mBounds, mStartAngle + mIndeterminateRotateOffset, mIndeterminateSweep, false, mProgressPaint);
}
}
/**
* Sets the mProgress of the mProgress bar.
*
* @param currentProgress the current mProgress you want to set
*/
public void setProgress(final float currentProgress) {
this.mProgress = currentProgress;
// Reset the determinate animation to approach the new mProgress
if (!mIndeterminate && mWasIndeterminate) {
mWasIndeterminate = false;
if (mProgressAnimation != null && mProgressAnimation.isRunning()) {
mProgressAnimation.cancel();
}
mProgressAnimation = FabUtil.createProgressAnimator(this, mActualProgress, currentProgress, this);
mProgressAnimation.start();
} else {
mActualProgress = currentProgress;
}
invalidate();
}
public void setMaxProgress(float maxProgress) {
this.mMaxProgress = maxProgress;
}
public float getMaxProgress() {
return this.mMaxProgress;
}
public void setIndeterminate(boolean indeterminate) {
if (mIndeterminate == indeterminate) {
return;
}
this.mIndeterminate = indeterminate;
if (indeterminate) {
mWasIndeterminate = true;
}
resetAnimation();
invalidate();
}
public void setAnimDuration(int animDuration) {
this.mAnimDuration = animDuration;
}
public void setProgressColor(int progressColor) {
this.mProgressColor = progressColor;
mProgressPaint.setColor(progressColor);
}
/**
* Starts the mProgress bar animation.
* (This is an alias of resetAnimation() so it does the same thing.)
*/
public void startAnimation() {
resetAnimation();
}
public void stopAnimation(boolean hideProgress) {
if (mStartAngleRotate != null && mStartAngleRotate.isRunning()) {
mStartAngleRotate.cancel();
}
if (mProgressAnimation != null && mProgressAnimation.isRunning()) {
mProgressAnimation.cancel();
}
if (mIndeterminateAnimator != null && mIndeterminateAnimator.isRunning()) {
mIndeterminateAnimator.cancel();
}
if (hideProgress) {
setRingWidth(0, false);
} else {
setRingWidth(0, true);
}
invalidate();
}
/**
* Resets the animation.
*/
public void resetAnimation() {
stopAnimation(false);
// Determinate animation
if (!mIndeterminate) {
// The cool 360 swoop animation at the start of the animation
mStartAngle = -90f;
mStartAngleRotate = FabUtil.createStartAngleAnimator(this, -90f, 270f, this);
mStartAngleRotate.start();
// The linear animation shown when mProgress is updated
mActualProgress = 0f;
mProgressAnimation = FabUtil.createProgressAnimator(this, mActualProgress, mProgress, this);
mProgressAnimation.start();
} else { // Indeterminate animation
mStartAngle = -90f;
mIndeterminateSweep = FabUtil.INDETERMINANT_MIN_SWEEP;
// Build the whole AnimatorSet
mIndeterminateAnimator = new AnimatorSet();
AnimatorSet prevSet = null, nextSet;
for (int k = 0; k < FabUtil.ANIMATION_STEPS; k++) {
nextSet = FabUtil.createIndeterminateAnimator(this, k, mAnimDuration, this);
AnimatorSet.Builder builder = mIndeterminateAnimator.play(nextSet);
if (prevSet != null) {
builder.after(prevSet);
}
prevSet = nextSet;
}
// Listen to end of animation so we can infinitely loop
mIndeterminateAnimator.addListener(new AnimatorListenerAdapter() {
boolean wasCancelled = false;
@Override
public void onAnimationCancel(Animator animation) {
wasCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (!wasCancelled) {
resetAnimation();
}
}
});
mIndeterminateAnimator.start();
}
}
@Override
public void onIndeterminateValuesChanged(float indeterminateSweep, float indeterminateRotateOffset, float startAngle, float progress) {
if (indeterminateSweep != -1) {
this.mIndeterminateSweep = indeterminateSweep;
}
if (indeterminateRotateOffset != -1) {
this.mIndeterminateRotateOffset = indeterminateRotateOffset;
}
if (startAngle != -1) {
this.mStartAngle = startAngle;
}
if (progress != -1) {
this.mActualProgress = progress;
if (Math.round(mActualProgress) == 100 && fabViewListener != null) {
fabViewListener.onProgressCompleted();
}
}
}
}