package com.lzy.demo.ui; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.support.v4.util.LruCache; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.View; import com.lzy.demo.R; public class ProgressPieView extends View { public interface OnProgressListener { public void onProgressChanged(int progress, int max); public void onProgressCompleted(); } /** * Fills the progress radially in a clockwise direction. */ public static final int FILL_TYPE_RADIAL = 0; /** * Fills the progress expanding from the center of the view. */ public static final int FILL_TYPE_CENTER = 1; public static final int SLOW_ANIMATION_SPEED = 50; public static final int MEDIUM_ANIMATION_SPEED = 25; public static final int FAST_ANIMATION_SPEED = 1; private static final int DEFAULT_MAX = 100; private static final int DEFAULT_PROGRESS = 0; private static final int DEFAULT_START_ANGLE = -90; private static final float DEFAULT_STROKE_WIDTH = 3f; private static final float DEFAULT_TEXT_SIZE = 14f; private static final int DEFAULT_VIEW_SIZE = 96; private static LruCache<String, Typeface> sTypefaceCache = new LruCache<String, Typeface>(8); private OnProgressListener mListener; private DisplayMetrics mDisplayMetrics; private int mMax = DEFAULT_MAX; private int mProgress = DEFAULT_PROGRESS; private int mStartAngle = DEFAULT_START_ANGLE; private boolean mInverted = false; private boolean mCounterclockwise = false; private boolean mShowStroke = true; private float mStrokeWidth = DEFAULT_STROKE_WIDTH; private boolean mShowText = true; private float mTextSize = DEFAULT_TEXT_SIZE; private String mText; private String mTypeface; private boolean mShowImage = true; private Drawable mImage; private Rect mImageRect; private Paint mStrokePaint; private Paint mTextPaint; private Paint mProgressPaint; private Paint mBackgroundPaint; private RectF mInnerRectF; private int mProgressFillType = FILL_TYPE_RADIAL; private int mAnimationSpeed = MEDIUM_ANIMATION_SPEED; private AnimationHandler mAnimationHandler = new AnimationHandler(); private int mViewSize; public ProgressPieView(Context context) { this(context, null); } public ProgressPieView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ProgressPieView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { mDisplayMetrics = context.getResources().getDisplayMetrics(); mStrokeWidth = mStrokeWidth * mDisplayMetrics.density; mTextSize = mTextSize * mDisplayMetrics.scaledDensity; final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressPieView); final Resources res = getResources(); mMax = a.getInteger(R.styleable.ProgressPieView_ppvMax, mMax); mProgress = a.getInteger(R.styleable.ProgressPieView_ppvProgress, mProgress); mStartAngle = a.getInt(R.styleable.ProgressPieView_ppvStartAngle, mStartAngle); mInverted = a.getBoolean(R.styleable.ProgressPieView_ppvInverted, mInverted); mCounterclockwise = a.getBoolean(R.styleable.ProgressPieView_ppvCounterclockwise, mCounterclockwise); mStrokeWidth = a.getDimension(R.styleable.ProgressPieView_ppvStrokeWidth, mStrokeWidth); mTypeface = a.getString(R.styleable.ProgressPieView_ppvTypeface); mTextSize = a.getDimension(R.styleable.ProgressPieView_android_textSize, mTextSize); mText = a.getString(R.styleable.ProgressPieView_android_text); mShowStroke = a.getBoolean(R.styleable.ProgressPieView_ppvShowStroke, mShowStroke); mShowText = a.getBoolean(R.styleable.ProgressPieView_ppvShowText, mShowText); mImage = a.getDrawable(R.styleable.ProgressPieView_ppvImage); int backgroundColor = Color.parseColor("#00000000"); backgroundColor = a.getColor(R.styleable.ProgressPieView_ppvBackgroundColor, backgroundColor); int progressColor = Color.parseColor("#33b5e5"); progressColor = a.getColor(R.styleable.ProgressPieView_ppvProgressColor, progressColor); int strokeColor = Color.parseColor("#33b5e5"); strokeColor = a.getColor(R.styleable.ProgressPieView_ppvStrokeColor, strokeColor); int textColor = Color.parseColor("#333333"); textColor = a.getColor(R.styleable.ProgressPieView_android_textColor, textColor); mProgressFillType = a.getInteger(R.styleable.ProgressPieView_ppvProgressFillType, mProgressFillType); a.recycle(); mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBackgroundPaint.setColor(backgroundColor); mBackgroundPaint.setStyle(Paint.Style.FILL); mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mProgressPaint.setColor(progressColor); mProgressPaint.setStyle(Paint.Style.FILL); mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mStrokePaint.setColor(strokeColor); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(mStrokeWidth); mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setColor(textColor); mTextPaint.setTextSize(mTextSize); mTextPaint.setTextAlign(Paint.Align.CENTER); mInnerRectF = new RectF(); mImageRect = new Rect(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = resolveSize(DEFAULT_VIEW_SIZE, widthMeasureSpec); int height = resolveSize(DEFAULT_VIEW_SIZE, heightMeasureSpec); mViewSize = Math.min(width, height); setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mInnerRectF.set(0, 0, mViewSize, mViewSize); mInnerRectF.offset((getWidth() - mViewSize) / 2, (getHeight() - mViewSize) / 2); if (mShowStroke) { final int halfBorder = (int) (mStrokePaint.getStrokeWidth() / 2f + 0.5f); mInnerRectF.inset(halfBorder, halfBorder); } float centerX = mInnerRectF.centerX(); float centerY = mInnerRectF.centerY(); canvas.drawArc(mInnerRectF, 0, 360, true, mBackgroundPaint); switch (mProgressFillType) { case FILL_TYPE_RADIAL: float sweepAngle = 360 * mProgress / mMax; if (mInverted) { sweepAngle = sweepAngle - 360; } if (mCounterclockwise) { sweepAngle = -sweepAngle; } canvas.drawArc(mInnerRectF, mStartAngle, sweepAngle, true, mProgressPaint); break; case FILL_TYPE_CENTER: float radius = (mViewSize / 2) * ((float) mProgress / mMax); if (mShowStroke) { radius = radius + 0.5f - mStrokePaint.getStrokeWidth(); } canvas.drawCircle(centerX, centerY, radius, mProgressPaint); break; default: throw new IllegalArgumentException("Invalid Progress Fill = " + mProgressFillType); } if (!TextUtils.isEmpty(mText) && mShowText) { if (!TextUtils.isEmpty(mTypeface)) { Typeface typeface = sTypefaceCache.get(mTypeface); if (null == typeface && null != getResources()) { AssetManager assets = getResources().getAssets(); if (null != assets) { typeface = Typeface.createFromAsset(assets, mTypeface); sTypefaceCache.put(mTypeface, typeface); } } mTextPaint.setTypeface(typeface); } int xPos = (int) centerX; int yPos = (int) (centerY - (mTextPaint.descent() + mTextPaint.ascent()) / 2); canvas.drawText(mText, xPos, yPos, mTextPaint); } if (null != mImage && mShowImage) { int drawableSize = mImage.getIntrinsicWidth(); mImageRect.set(0, 0, drawableSize, drawableSize); mImageRect.offset((getWidth() - drawableSize) / 2, (getHeight() - drawableSize) / 2); mImage.setBounds(mImageRect); mImage.draw(canvas); } if (mShowStroke) { canvas.drawOval(mInnerRectF, mStrokePaint); } } /** * Gets the maximum progress value. */ public int getMax() { return mMax; } /** * Sets the maximum progress value. Defaults to 100. */ public void setMax(int max) { if (max <= 0 || max < mProgress) { throw new IllegalArgumentException(String.format("Max (%d) must be > 0 and >= %d", max, mProgress)); } mMax = max; invalidate(); } /** * Sets the animation speed used in the animateProgressFill method. */ public void setAnimationSpeed(int animationSpeed) { this.mAnimationSpeed = animationSpeed; } /** * Returns the current animation speed used in animateProgressFill method. */ public int getAnimationSpeed() { return this.mAnimationSpeed; } /** * Animates a progress fill of the view, using a Handler. */ public void animateProgressFill() { mAnimationHandler.removeMessages(0); mAnimationHandler.setAnimateTo(mMax); mAnimationHandler.sendEmptyMessage(0); invalidate(); } /** * Animates a progress fill of the view, using a Handler. * * @param animateTo - the progress value the animation should stop at (0 - MAX) */ public void animateProgressFill(int animateTo) { mAnimationHandler.removeMessages(0); if (animateTo > mMax || animateTo < 0) { throw new IllegalArgumentException(String.format("Animation progress (%d) is greater than the max progress (%d) or lower than 0 ", animateTo, mMax)); } mAnimationHandler.setAnimateTo(animateTo); mAnimationHandler.sendEmptyMessage(0); invalidate(); } /** * Stops the views animation. */ public void stopAnimating() { mAnimationHandler.removeMessages(0); mAnimationHandler.setAnimateTo(mProgress); invalidate(); } /** * Gets the current progress from 0 to max. */ public int getProgress() { return mProgress; } /** * Sets the current progress (must be between 0 and max). */ public void setProgress(int progress) { if (progress > mMax || progress < 0) { throw new IllegalArgumentException(String.format("Progress (%d) must be between %d and %d", progress, 0, mMax)); } mProgress = progress; if (null != mListener) { if (mProgress == mMax) { mListener.onProgressCompleted(); } else { mListener.onProgressChanged(mProgress, mMax); } } invalidate(); } /** * Gets the start angle the {@link #FILL_TYPE_RADIAL} uses. */ public int getStartAngle() { return mStartAngle; } /** * Sets the start angle the {@link #FILL_TYPE_RADIAL} uses. * * @param startAngle start angle in degrees */ public void setStartAngle(int startAngle) { mStartAngle = startAngle; } /** * Gets the inverted state. */ public boolean isInverted() { return mInverted; } /** * Sets the inverted state. * * @param inverted draw the progress inverted or not */ public void setInverted(boolean inverted) { mInverted = inverted; } /** * Gets the counterclockwise state. */ public boolean isCounterclockwise() { return mCounterclockwise; } /** * Sets the counterclockwise state. * * @param counterclockwise draw the progress counterclockwise or not */ public void setCounterclockwise(boolean counterclockwise) { mCounterclockwise = counterclockwise; } /** * Gets the color used to display the progress of the view. */ public int getProgressColor() { return mProgressPaint.getColor(); } /** * Sets the color used to display the progress of the view. * * @param color - color of the progress part of the view */ public void setProgressColor(int color) { mProgressPaint.setColor(color); invalidate(); } /** * Gets the color used to display the background of the view. */ public int getBackgroundColor() { return mBackgroundPaint.getColor(); } /** * Sets the color used to display the background of the view. * * @param color - color of the background part of the view */ public void setBackgroundColor(int color) { mBackgroundPaint.setColor(color); invalidate(); } /** * Gets the color used to display the text of the view. */ public int getTextColor() { return mTextPaint.getColor(); } /** * Sets the color used to display the text of the view. * * @param color - color of the text part of the view */ public void setTextColor(int color) { mTextPaint.setColor(color); invalidate(); } /** * Gets the text size in sp. */ public float getTextSize() { return mTextSize; } /** * Sets the text size. * * @param sizeSp in sp for the text */ public void setTextSize(int sizeSp) { mTextSize = sizeSp * mDisplayMetrics.scaledDensity; mTextPaint.setTextSize(mTextSize); invalidate(); } /** * Gets the text of the view. */ public String getText() { return mText; } /** * Sets the text of the view. * * @param text to be displayed in the view */ public void setText(String text) { mText = text; invalidate(); } /** * Gets the typeface of the text. */ public String getTypeface() { return mTypeface; } /** * Sets the text typeface. * - i.e. fonts/Roboto/Roboto-Regular.ttf * * @param typeface that the text is displayed in */ public void setTypeface(String typeface) { mTypeface = typeface; invalidate(); } /** * Gets the show text state. */ public boolean isTextShowing() { return mShowText; } /** * Sets the show text state. * * @param showText show or hide text */ public void setShowText(boolean showText) { mShowText = showText; invalidate(); } /** * Get the color used to display the stroke of the view. */ public int getStrokeColor() { return mStrokePaint.getColor(); } /** * Sets the color used to display the stroke of the view. * * @param color - color of the stroke part of the view */ public void setStrokeColor(int color) { mStrokePaint.setColor(color); invalidate(); } /** * Gets the stroke width in dp. */ public float getStrokeWidth() { return mStrokeWidth; } /** * Set the stroke width. * * @param widthDp in dp for the pie border */ public void setStrokeWidth(int widthDp) { mStrokeWidth = widthDp * mDisplayMetrics.density; mStrokePaint.setStrokeWidth(mStrokeWidth); invalidate(); } /** * Gets the show stroke state. */ public boolean isStrokeShowing() { return mShowStroke; } /** * Sets the show stroke state. * * @param showStroke show or hide stroke */ public void setShowStroke(boolean showStroke) { mShowStroke = showStroke; invalidate(); } /** * Gets the drawable of the view. */ public Drawable getImageDrawable() { return mImage; } /** * Sets the drawable of the view. * * @param image drawable of the view */ public void setImageDrawable(Drawable image) { mImage = image; invalidate(); } /** * Sets the drawable of the view. * * @param resId resource id of the view's drawable */ public void setImageResource(int resId) { if (null != getResources()) { mImage = getResources().getDrawable(resId); invalidate(); } } /** * Gets the show image state. */ public boolean isImageShowing() { return mShowImage; } /** * Sets the show image state. * * @param showImage show or hide image */ public void setShowImage(boolean showImage) { mShowImage = showImage; invalidate(); } /** * Gets the progress fill type. */ public int getProgressFillType() { return mProgressFillType; } /** * Sets the progress fill type. * * @param fillType one of {@link #FILL_TYPE_CENTER}, {@link #FILL_TYPE_RADIAL} */ public void setProgressFillType(int fillType) { mProgressFillType = fillType; } /** * Sets the progress listner. * * @param listener progress listener */ public void setOnProgressListener(OnProgressListener listener) { mListener = listener; } /** * Handler used to perform the fill animation. */ private class AnimationHandler extends Handler { private int mAnimateTo; public void setAnimateTo(int animateTo) { mAnimateTo = animateTo; } @Override public void handleMessage(Message msg) { if (mProgress > mAnimateTo) { setProgress(mProgress - 1); sendEmptyMessageDelayed(0, mAnimationSpeed); } else if (mProgress < mAnimateTo) { setProgress(mProgress + 1); sendEmptyMessageDelayed(0, mAnimationSpeed); } else { removeMessages(0); } } } }