package com.roboo.like.google.progressbutton;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.ProgressBar;
import com.roboo.like.google.R;
public class ProcessButton extends Button
{
private float mCornerRadius;
private StateListDrawable mNormalDrawable;
private StateListDrawable mProgressDrawable;
private StateListDrawable mCompleteDrawable;
private StateListDrawable mErrorDrawable;
private CharSequence mNormalText;
private int mProgress;
private Rect mBounds = new Rect();
private static final int MIN_PROGRESS = 0;
private static final int MAX_PROGRESS = 100;
private static final int TYPE_ERROR = -1;
private static final int TYPE_NORMAL = 0;
private static final int TYPE_PROGRESS = 1;
private static final int TYPE_PRESSED = 2;
private static final int TYPE_COMPLETE = 3;
public static final int TYPE_DRAW_DEFAULT = 0;
public static final int TYPE_DRAW_PROGRESS = 1;
public static final int TYPE_DRAW_PROGRESS_HORIZONAL = 2;
public static final int TYPE_DRAW_PROGRESS_VERTICAL = 3;
private CharSequence mLoadingText;
private CustomProgressBar mProgressBar;
private CharSequence mCompleteText;
private CharSequence mErrorText;
private int mDrawType;
/** 默认按钮按下时的颜色 */
private static final int DEFAULT_PRESSED_COLOR = 0xFFFF0000;
private static final int DEFAULT_NORMAL_COLOR = 0xFF0000FF;
protected static final int DEFAULT_PROGRESS_COLOR = 0xFFFF0000;
protected static final int DEFAULT_COMPLETE_COLOR = 0xFF99CC00;
protected static final int DEFAULT_ERROR_COLOR = 0xFFFF4444;
public ProcessButton(Context context)
{
this(context, null);
}
public ProcessButton(Context context, AttributeSet attrs)
{
super(context, attrs);
mNormalDrawable = new StateListDrawable();
mProgressDrawable = new StateListDrawable();
mCompleteDrawable = new StateListDrawable();
mErrorDrawable = new StateListDrawable();
mNormalText = getText().toString();
if (null != attrs)
{
initAttributes(context, attrs);
}
setBackgroundDrawable(mNormalDrawable);
}
private void initAttributes(Context context, AttributeSet attrs)
{
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressButton, 0, 0);
if (null == typedArray)
{
return;
}
try
{
float defaultCorner = context.getResources().getDimension(R.dimen.corner_radius);
mCornerRadius = typedArray.getDimension(R.styleable.ProgressButton_cornerRadius, defaultCorner);
mLoadingText = typedArray.getString(R.styleable.ProgressButton_textProgress);
mCompleteText = typedArray.getString(R.styleable.ProgressButton_textComplete);
mDrawType = typedArray.getInt(R.styleable.ProgressButton_drawType, TYPE_DRAW_DEFAULT);
mNormalDrawable.addState(new int[] { android.R.attr.state_pressed }, createDrawable(typedArray, TYPE_PRESSED));
mNormalDrawable.addState(new int[] {}, createDrawable(typedArray, TYPE_NORMAL));
mProgressDrawable.addState(new int[] {}, createDrawable(typedArray, TYPE_PROGRESS));
mCompleteDrawable.addState(new int[] {}, createDrawable(typedArray, TYPE_COMPLETE));
mErrorDrawable.addState(new int[] {}, createDrawable(typedArray, TYPE_ERROR));
}
finally
{
typedArray.recycle();
}
}
private Drawable createProgressDrawable(TypedArray typedArray)
{
GradientDrawable progressDrawable = (GradientDrawable) new GradientDrawable().mutate();
progressDrawable.setShape(GradientDrawable.RECTANGLE);
int progressColor = typedArray.getColor(R.styleable.ProgressButton_colorPressed, DEFAULT_PROGRESS_COLOR);
progressDrawable.setColor(progressColor);
progressDrawable.setCornerRadius(mCornerRadius);
return progressDrawable;
}
private Drawable createDrawable(TypedArray typedArray, int type)
{
Drawable drawable = null;
switch (type)
{
case TYPE_PROGRESS:
drawable = createProgressDrawable(typedArray);
break;
case TYPE_NORMAL:
drawable = createNormalDrawable(typedArray);
break;
case TYPE_PRESSED:
drawable = createPressedDrawable(typedArray);
break;
case TYPE_COMPLETE:
drawable = createCompleteDrawable(typedArray);
break;
case TYPE_ERROR:
drawable = createErrorDrawable(typedArray);
break;
}
return drawable;
}
private Drawable createErrorDrawable(TypedArray typedArray)
{
GradientDrawable completeDrawable = (GradientDrawable) new GradientDrawable().mutate();
completeDrawable.setShape(GradientDrawable.RECTANGLE);
int completeColor = typedArray.getColor(R.styleable.ProgressButton_colorError, DEFAULT_ERROR_COLOR);
completeDrawable.setColor(completeColor);
completeDrawable.setCornerRadius(mCornerRadius);
return completeDrawable;
}
private Drawable createCompleteDrawable(TypedArray typedArray)
{
GradientDrawable completeDrawable = (GradientDrawable) new GradientDrawable().mutate();
completeDrawable.setShape(GradientDrawable.RECTANGLE);
int completeColor = typedArray.getColor(R.styleable.ProgressButton_colorComplete, DEFAULT_COMPLETE_COLOR);
completeDrawable.setColor(completeColor);
completeDrawable.setCornerRadius(mCornerRadius);
return completeDrawable;
}
private Drawable createPressedDrawable(TypedArray typedArray)
{
GradientDrawable pressedDrawable = (GradientDrawable) new GradientDrawable().mutate();
pressedDrawable.setShape(GradientDrawable.RECTANGLE);
int pressedColor = typedArray.getColor(R.styleable.ProgressButton_colorPressed, DEFAULT_PRESSED_COLOR);
pressedDrawable.setColor(pressedColor);
pressedDrawable.setCornerRadius(mCornerRadius);
return pressedDrawable;
}
private Drawable createNormalDrawable(TypedArray typedArray)
{
GradientDrawable drawableTop = (GradientDrawable) new GradientDrawable().mutate();
GradientDrawable drawableBottom = (GradientDrawable) new GradientDrawable().mutate();
int colorPressed = typedArray.getColor(R.styleable.ProgressButton_colorPressed, DEFAULT_PRESSED_COLOR);
int colorNormal = typedArray.getColor(R.styleable.ProgressButton_colorNormal, DEFAULT_NORMAL_COLOR);
drawableTop.setShape(GradientDrawable.RECTANGLE);
drawableTop.setCornerRadius(mCornerRadius);
drawableTop.setColor(colorPressed);
drawableBottom.setShape(GradientDrawable.RECTANGLE);
drawableBottom.setCornerRadius(mCornerRadius);
drawableBottom.setColor(colorNormal);
LayerDrawable drawableNormal = new LayerDrawable(new Drawable[] { drawableTop, drawableBottom });
return drawableNormal;
}
public void setProgress(int progress)
{
mProgress = progress;
if (mProgress == MIN_PROGRESS)
{
onNormalState();
}
else if (mProgress == MAX_PROGRESS)
{
onCompleteState();
}
else if (mProgress < MIN_PROGRESS)
{
onErrorState();
}
else
{
onProgress();
}
invalidate();
}
public CharSequence getNormalText()
{
return mNormalText;
}
public void setNormalText(CharSequence normalText)
{
this.mNormalText = normalText;
}
public int getDrawType()
{
return mDrawType;
}
public void setDrawType(int drawType)
{
this.mDrawType = drawType;
}
public float getCornerRadius()
{
return mCornerRadius;
}
public StateListDrawable getNormalDrawable()
{
return mNormalDrawable;
}
public StateListDrawable getProgressDrawable()
{
return mProgressDrawable;
}
protected void onErrorState()
{
if (getErrorText() != null)
{
setText(getErrorText());
}
setBackgroundDrawable(getErrorDrawable());
}
protected void onProgress()
{
if (getLoadingText() != null)
{
setText(getLoadingText());
}
setBackgroundDrawable(getNormalDrawable());
}
public void onCompleteState()
{
if (getCompleteText() != null)
{
setText(getCompleteText());
}
setBackgroundDrawable(getCompleteDrawable());
}
public void onNormalState()
{
if (getNormalText() != null)
{
setText(getNormalText());
}
setBackgroundDrawable(getNormalDrawable());
}
@Override
protected void onDraw(Canvas canvas)
{
if (mProgress > MIN_PROGRESS && mProgress < MAX_PROGRESS)
{
drawProgress(canvas);
}
super.onDraw(canvas);
}
public void setDrawProgressType(int drawType)
{
mDrawType = drawType;
}
public void drawProgress(Canvas canvas)
{
switch (mDrawType)
{
case TYPE_DRAW_DEFAULT:
drawEndlessProgress(canvas);
break;
case TYPE_DRAW_PROGRESS:
drawLineProgress(canvas);
break;
case TYPE_DRAW_PROGRESS_HORIZONAL:
drawHorizonalProgress(canvas);
break;
case TYPE_DRAW_PROGRESS_VERTICAL:
drawVerticalProgress(canvas);
break;
}
}
private void drawEndlessProgress(Canvas canvas)
{
double indicatorHeightPercent = 0.05; // 5%
int bottom = (int) (getMeasuredHeight() - getMeasuredHeight() * indicatorHeightPercent);
if (mProgressBar == null)
{
mProgressBar = new CustomProgressBar(this);
mProgressBar.setBounds(0, bottom, getMeasuredWidth(), getMeasuredHeight());
mProgressBar.setColorScheme(DEFAULT_ERROR_COLOR, DEFAULT_PRESSED_COLOR, DEFAULT_PROGRESS_COLOR, DEFAULT_COMPLETE_COLOR);
mProgressBar.start();
}
if (getProgress() > 0)
{
mProgressBar.draw(canvas);
}
}
private void drawLineProgress(Canvas canvas)
{
float scale = (float) getProgress() / (float) getMaxProgress();
float indicatorWidth = (float) getMeasuredWidth() * scale;
double indicatorHeightPercent = 0.05; // 5%
int bottom = (int) (getMeasuredHeight() - getMeasuredHeight() * indicatorHeightPercent);
getProgressDrawable().setBounds(0, bottom, (int) indicatorWidth, getMeasuredHeight());
getProgressDrawable().draw(canvas);
}
public void setBounds(int left, int top, int right, int bottom)
{
mBounds.left = left;
mBounds.top = top;
mBounds.right = right;
mBounds.bottom = bottom;
}
private void drawHorizonalProgress(Canvas canvas)
{
float scale = (float) getProgress() / (float) getMaxProgress();
float indicatorWidth = (float) getMeasuredWidth() * scale;
getProgressDrawable().setBounds(0, 0, (int) indicatorWidth, getMeasuredHeight());
getProgressDrawable().draw(canvas);
}
private void drawVerticalProgress(Canvas canvas)
{
float scale = (float) getProgress() / (float) getMaxProgress();
float indicatorHeight = (float) getMeasuredHeight() * scale;
getProgressDrawable().setBounds(0, 0, getMeasuredWidth(), (int) indicatorHeight);
getProgressDrawable().draw(canvas);
}
public int getProgress()
{
return mProgress;
}
public int getMaxProgress()
{
return MAX_PROGRESS;
}
public int getMinProgress()
{
return MIN_PROGRESS;
}
public StateListDrawable getCompleteDrawable()
{
return mCompleteDrawable;
}
public CharSequence getLoadingText()
{
return mLoadingText;
}
public CharSequence getCompleteText()
{
return mCompleteText;
}
public void setProgressDrawable(StateListDrawable progressDrawable)
{
mProgressDrawable = progressDrawable;
}
public void setCompleteDrawable(StateListDrawable completeDrawable)
{
mCompleteDrawable = completeDrawable;
}
public void setLoadingText(CharSequence loadingText)
{
mLoadingText = loadingText;
}
public void setCompleteText(CharSequence completeText)
{
mCompleteText = completeText;
}
public StateListDrawable getErrorDrawable()
{
return mErrorDrawable;
}
public void setErrorDrawable(StateListDrawable errorDrawable)
{
mErrorDrawable = errorDrawable;
}
public CharSequence getErrorText()
{
return mErrorText;
}
public void setErrorText(CharSequence errorText)
{
mErrorText = errorText;
}
@Override
public Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.mProgress = mProgress;
return savedState;
}
@Override
public void onRestoreInstanceState(Parcelable state)
{
if (state instanceof SavedState)
{
SavedState savedState = (SavedState) state;
mProgress = savedState.mProgress;
super.onRestoreInstanceState(savedState.getSuperState());
setProgress(mProgress);
}
else
{
super.onRestoreInstanceState(state);
}
}
public static class SavedState extends BaseSavedState
{
private int mProgress;
public SavedState(Parcelable parcel)
{
super(parcel);
}
private SavedState(Parcel in)
{
super(in);
mProgress = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeInt(mProgress);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>()
{
@Override
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
public static class CustomProgressBar
{
// The duration of the animation cycle.
private static final int ANIMATION_DURATION_MS = 2000;
// The duration of the animation to clear the bar.
private static final int FINISH_ANIMATION_DURATION_MS = 1000;
// Interpolator for varying the speed of the animation.
private static final Interpolator INTERPOLATOR = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
private final RectF mClipRect = new RectF();
private float mTriggerPercentage;
private long mStartTime;
private long mFinishTime;
private boolean mRunning;
// Colors used when rendering the animation,
private int mColor1;
private int mColor2;
private int mColor3;
private int mColor4;
private View mParent;
private int[] mColors;
private Rect mBounds = new Rect();
public CustomProgressBar(View parent)
{
mParent = parent;
}
public void setProgressColors(int[] colors)
{
mColors = colors;
}
void setColorScheme(int color1, int color2, int color3, int color4)
{
mColor1 = color1;
mColor2 = color2;
mColor3 = color3;
mColor4 = color4;
}
/**
* Start showing the progress animation.
*/
void start()
{
if (!mRunning)
{
mTriggerPercentage = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mRunning = true;
mParent.postInvalidate();
}
}
void draw(Canvas canvas)
{
final int width = mBounds.width();
final int height = mBounds.height();
final int cx = width / 2;
final int cy = height / 2;
boolean drawTriggerWhileFinishing = false;
int restoreCount = canvas.save();
canvas.clipRect(mBounds);
if (mRunning || (mFinishTime > 0))
{
long now = AnimationUtils.currentAnimationTimeMillis();
long elapsed = (now - mStartTime) % ANIMATION_DURATION_MS;
long iterations = (now - mStartTime) / ANIMATION_DURATION_MS;
float rawProgress = (elapsed / (ANIMATION_DURATION_MS / 100f));
// If we're not running anymore, that means we're running through
// the finish animation.
if (!mRunning)
{
// If the finish animation is done, don't draw anything, and
// don't repost.
if ((now - mFinishTime) >= FINISH_ANIMATION_DURATION_MS)
{
mFinishTime = 0;
return;
}
// Otherwise, use a 0 opacity alpha layer to clear the animation
// from the inside out. This layer will prevent the circles from
// drawing within its bounds.
long finishElapsed = (now - mFinishTime) % FINISH_ANIMATION_DURATION_MS;
float finishProgress = (finishElapsed / (FINISH_ANIMATION_DURATION_MS / 100f));
float pct = (finishProgress / 100f);
// Radius of the circle is half of the screen.
float clearRadius = width / 2 * INTERPOLATOR.getInterpolation(pct);
mClipRect.set(cx - clearRadius, 0, cx + clearRadius, height);
canvas.saveLayerAlpha(mClipRect, 0, 0);
// Only draw the trigger if there is a space in the center of
// this refreshing view that needs to be filled in by the
// trigger. If the progress view is just still animating, let it
// continue animating.
drawTriggerWhileFinishing = true;
}
// First fill in with the last color that would have finished drawing.
if (iterations == 0)
{
canvas.drawColor(mColor1);
}
else
{
if (rawProgress >= 0 && rawProgress < 25)
{
canvas.drawColor(mColor4);
}
else if (rawProgress >= 25 && rawProgress < 50)
{
canvas.drawColor(mColor1);
}
else if (rawProgress >= 50 && rawProgress < 75)
{
canvas.drawColor(mColor2);
}
else
{
canvas.drawColor(mColor3);
}
}
// Then draw up to 4 overlapping concentric circles of varying radii, based on how far
// along we are in the cycle.
// progress 0-50 draw mColor2
// progress 25-75 draw mColor3
// progress 50-100 draw mColor4
// progress 75 (wrap to 25) draw mColor1
if ((rawProgress >= 0 && rawProgress <= 25))
{
float pct = (((rawProgress + 25) * 2) / 100f);
drawCircle(canvas, cx, cy, mColor1, pct);
}
if (rawProgress >= 0 && rawProgress <= 50)
{
float pct = ((rawProgress * 2) / 100f);
drawCircle(canvas, cx, cy, mColor2, pct);
}
if (rawProgress >= 25 && rawProgress <= 75)
{
float pct = (((rawProgress - 25) * 2) / 100f);
drawCircle(canvas, cx, cy, mColor3, pct);
}
if (rawProgress >= 50 && rawProgress <= 100)
{
float pct = (((rawProgress - 50) * 2) / 100f);
drawCircle(canvas, cx, cy, mColor4, pct);
}
if ((rawProgress >= 75 && rawProgress <= 100))
{
float pct = (((rawProgress - 75) * 2) / 100f);
drawCircle(canvas, cx, cy, mColor1, pct);
}
if (mTriggerPercentage > 0 && drawTriggerWhileFinishing)
{
// There is some portion of trigger to draw. Restore the canvas,
// then draw the trigger. Otherwise, the trigger does not appear
// until after the bar has finished animating and appears to
// just jump in at a larger width than expected.
canvas.restoreToCount(restoreCount);
restoreCount = canvas.save();
canvas.clipRect(mBounds);
drawTrigger(canvas, cx, cy);
}
// Keep running until we finish out the last cycle.
ViewCompat.postInvalidateOnAnimation(mParent);
}
else
{
// Otherwise if we're in the middle of a trigger, draw that.
if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0)
{
drawTrigger(canvas, cx, cy);
}
}
canvas.restoreToCount(restoreCount);
}
private void drawTrigger(Canvas canvas, int cx, int cy)
{
mPaint.setColor(mColor1);
canvas.drawCircle(cx, cy, cx * mTriggerPercentage, mPaint);
}
/**
* Draws a circle centered in the view.
*
* @param canvas
* the canvas to draw on
* @param cx
* the center x coordinate
* @param cy
* the center y coordinate
* @param color
* the color to draw
* @param pct
* the percentage of the view that the circle should cover
*/
private void drawCircle(Canvas canvas, float cx, float cy, int color, float pct)
{
mPaint.setColor(color);
canvas.save();
canvas.translate(cx, cy);
float radiusScale = INTERPOLATOR.getInterpolation(pct);
canvas.scale(radiusScale, radiusScale);
canvas.drawCircle(0, 0, cx, mPaint);
canvas.restore();
}
/**
* Set the drawing bounds of this SwipeProgressBar.
*/
void setBounds(int left, int top, int right, int bottom)
{
mBounds.left = left;
mBounds.top = top;
mBounds.right = right;
mBounds.bottom = bottom;
}
}
}