/* * Copyright (c) 2015. TedYin * 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. */ package me.tedyin.circleprogressbarlib; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; import android.util.AttributeSet; import android.view.View; /** * 圆形进度条 * Created by ted on 3/24/15. */ public class CircleProgressBar extends View { private static final String KEY_INSTANCE_STATE = "instance_state"; private static final String KEY_STATE_CURRENT_PROGRESS = "state_current_progress"; private static final String KEY_STATE_ANGLE_STEP = "state_angle_step"; private static final String KEY_STATE_NEED_SHOW_TEXT = "state_need_show_text"; private static final String KEY_STATE_NEED_ANIM = "state_need_anim"; private static final float MAX_PROGRESS = 100f; private static Handler mHandler = new Handler(Looper.getMainLooper()); private Context mContext; private int mCpbWholeBackgroundColor = Color.parseColor("#ffeeeaff"); private int mCpbBackgroundColor = Color.parseColor("#7df5f5f5"); private int mCpbForegroundColor = Color.parseColor("#0dfb7d"); private int mCpbProgressTextColor; private int mCpbStrokeWidth = 10;// default stroke width private int mCpbStartAngle = -90;// default 12 o'clock private int mCpbMaxAngle = 360;// default complete circle private boolean mCpbNeedAnim = true;// default start anim private boolean mCpbNeedShowText = true;// default show text private RectF mWholeRectF, mForegroundRectF, mBackgroundRectF; private Paint mCpbWholeBackgroundPaint; private Paint mCpbBackgroundPaint; private Paint mCpbForegroundPaint; private Paint mCpbTextPaint; private int mAngleStep = 0; private int mMinWidth; private int mCurrentProgress = 0; private int[] mColorScheme; private AnimRunnable mAnimRunnable; private LoadingCallBack mLoadingCallBack; public CircleProgressBar(Context context) { super(context); this.mContext = context; init(); } public CircleProgressBar(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; parseAttributes(attrs); init(); } private void parseAttributes(AttributeSet attrs) { TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar); mCpbWholeBackgroundColor = a.getColor(R.styleable.CircleProgressBar_cpbWholeBackgroundColor, mCpbWholeBackgroundColor); mCpbForegroundColor = a.getColor(R.styleable.CircleProgressBar_cpbForegroundColor, mCpbForegroundColor); mCpbBackgroundColor = a.getColor(R.styleable.CircleProgressBar_cpbBackgroundColor, mCpbBackgroundColor); mCpbProgressTextColor = a.getColor(R.styleable.CircleProgressBar_cpbProgressTextColor, mCpbProgressTextColor); mCpbStrokeWidth = a.getInt(R.styleable.CircleProgressBar_cpbStrokeWidth, mCpbStrokeWidth); mCpbStartAngle = a.getInt(R.styleable.CircleProgressBar_cpbStartAngle, mCpbStartAngle); mCpbMaxAngle = a.getInt(R.styleable.CircleProgressBar_cpbMaxAngle, mCpbMaxAngle); mCpbNeedShowText = a.getBoolean(R.styleable.CircleProgressBar_cpbNeedShowText, mCpbNeedShowText); mCpbNeedAnim = a.getBoolean(R.styleable.CircleProgressBar_cpbNeedAnim, mCpbNeedAnim); mCpbNeedAnim = mCpbMaxAngle % 360 == 0 && mCpbNeedAnim; mCpbProgressTextColor = mCpbProgressTextColor == 0 ? mCpbForegroundColor : mCpbProgressTextColor; a.recycle(); } private void init() { initWholeBackgroundPaint(); initProgressBackgroundPaint(); initProgressForegroundPaint(); initTextPaint(); initRectF(); initAnim(); } // init progress background paint private void initWholeBackgroundPaint() { mCpbWholeBackgroundPaint = new Paint(); mCpbWholeBackgroundPaint.setAntiAlias(true); mCpbWholeBackgroundPaint.setColor(mCpbWholeBackgroundColor); } // init progress background paint private void initProgressBackgroundPaint() { mCpbBackgroundPaint = new Paint(); mCpbBackgroundPaint.setAntiAlias(true); mCpbBackgroundPaint.setStyle(Paint.Style.STROKE); mCpbBackgroundPaint.setStrokeWidth(mCpbStrokeWidth); mCpbBackgroundPaint.setColor(mCpbBackgroundColor); } // init progress foreground paint private void initProgressForegroundPaint() { mCpbForegroundPaint = new Paint(); mCpbForegroundPaint.setAntiAlias(true); mCpbForegroundPaint.setDither(true); mCpbForegroundPaint.setStyle(Paint.Style.STROKE); mCpbForegroundPaint.setStrokeCap(Paint.Cap.ROUND); mCpbForegroundPaint.setStrokeWidth(mCpbStrokeWidth); mCpbForegroundPaint.setColor(mCpbForegroundColor); } // init text paint private void initTextPaint() { mCpbTextPaint = new Paint(); mCpbTextPaint.setAntiAlias(true); mCpbTextPaint.setColor(mCpbProgressTextColor); } // init rectF private void initRectF() { mBackgroundRectF = new RectF(); mForegroundRectF = new RectF(); mWholeRectF = new RectF(); } // init Animation Runnable private void initAnim() { if (mCpbNeedAnim) { mAnimRunnable = new AnimRunnable(); } } // init shader private void initShader() { Shader colorShader = null; // init color shader if (mColorScheme != null && mColorScheme.length != 0) { float end = mMinWidth - mCpbStrokeWidth / 2; colorShader = new LinearGradient(0, 0, end, end, mColorScheme, null, Shader.TileMode.CLAMP); } // set shader if (colorShader != null) { setShader(colorShader); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); startAnimIfNeed(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { mMinWidth = Math.min(getWidth(), getHeight()); int rLeft = mCpbStrokeWidth / 2; int rTop = mCpbStrokeWidth / 2; int rRight = mMinWidth - mCpbStrokeWidth / 2; int rBottom = mMinWidth - mCpbStrokeWidth / 2; mBackgroundRectF.set(rLeft, rTop, rRight, rBottom); mForegroundRectF.set(rLeft, rTop, rRight, rBottom); mWholeRectF.set(mCpbStrokeWidth / 2, mCpbStrokeWidth / 2, mMinWidth - mCpbStrokeWidth / 2, mMinWidth - mCpbStrokeWidth / 2); initShader(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawCpbWholeBackground(canvas); drawCpbBackground(canvas); drawCpbForeground(canvas); drawProgressText(canvas); } private void drawCpbWholeBackground(Canvas canvas) { canvas.drawCircle(mForegroundRectF.centerX(), mForegroundRectF.centerY(), mForegroundRectF.height() / 2, mCpbWholeBackgroundPaint); } private void drawProgressText(Canvas canvas) { if (!mCpbNeedShowText) return; String text = String.valueOf(mCurrentProgress); int textSize = (int) ((mMinWidth - mCpbStrokeWidth * 2) / 2.5); mCpbTextPaint.setTextSize(textSize); Paint.FontMetrics fm = mCpbTextPaint.getFontMetrics(); float textHeight = (float) Math.ceil(fm.descent - fm.top); float textWidth = mCpbTextPaint.measureText(text); float x = (mMinWidth - textWidth) / 2; float y = (mMinWidth - textHeight) / 2 + textSize; canvas.drawText(text, x, y, mCpbTextPaint); } private void drawCpbForeground(Canvas canvas) { int startAngle = mAngleStep + mCpbStartAngle; int sweepAngle = (int) ((mCurrentProgress / MAX_PROGRESS) * mCpbMaxAngle); canvas.drawArc(mForegroundRectF, startAngle, sweepAngle, false, mCpbForegroundPaint); } private void drawCpbBackground(Canvas canvas) { canvas.drawArc(mBackgroundRectF, mCpbStartAngle, mCpbMaxAngle, false, mCpbBackgroundPaint); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mAnimRunnable != null) { mHandler.removeCallbacks(mAnimRunnable); } } @Override protected Parcelable onSaveInstanceState() { Bundle state = new Bundle(); state.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState()); state.putInt(KEY_STATE_CURRENT_PROGRESS, mCurrentProgress); state.putInt(KEY_STATE_ANGLE_STEP, mAngleStep); state.putBoolean(KEY_STATE_NEED_SHOW_TEXT, mCpbNeedShowText); state.putBoolean(KEY_STATE_NEED_ANIM, mCpbNeedAnim); return state; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; mCurrentProgress = bundle.getInt(KEY_STATE_CURRENT_PROGRESS); mAngleStep = bundle.getInt(KEY_STATE_ANGLE_STEP); mCpbNeedAnim = bundle.getBoolean(KEY_STATE_NEED_ANIM); mCpbNeedShowText = bundle.getBoolean(KEY_STATE_NEED_SHOW_TEXT); super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE)); } else { super.onRestoreInstanceState(state); } } private void startAnimIfNeed() { if (mAnimRunnable != null) { mHandler.removeCallbacks(mAnimRunnable); } if (mCpbNeedAnim) { mHandler.post(mAnimRunnable); } } private class AnimRunnable implements Runnable { @Override public void run() { if (mCurrentProgress >= MAX_PROGRESS) { mCurrentProgress = (int) MAX_PROGRESS; invalidateView(); mHandler.removeCallbacks(this); if (mLoadingCallBack != null) { mLoadingCallBack.loadingComplete(CircleProgressBar.this); } } else { invalidateView(); mHandler.postDelayed(this, 12); } } private void invalidateView() { mAngleStep += 2; invalidate(); } } private void setShader(Shader shader) { mCpbForegroundPaint.setShader(shader); if (mCpbProgressTextColor != mCpbForegroundColor || !mCpbNeedShowText) return; mCpbTextPaint.setShader(shader); } /** * invalidate view */ public void invalidateUi() { if (Looper.getMainLooper() == Looper.myLooper()) { invalidate(); } else { postInvalidate(); } } public void setProgress(int progress) { this.mCurrentProgress = progress > MAX_PROGRESS ? (int) MAX_PROGRESS : progress; if (!mCpbNeedAnim) { invalidateUi(); } } /** * set color scheme * * @param colorScheme colors */ public void setColorScheme(int... colorScheme) { mColorScheme = colorScheme; } /** * loading complete callback * * @param callBack callback */ public void setLoadingCallBack(LoadingCallBack callBack) { this.mLoadingCallBack = callBack; } public interface LoadingCallBack { public void loadingComplete(View v); } }