背景:
最近做了個如圖所示環形進度條,下面來記錄一下實現過程,廢話不多說,先上圖
除了圖中所示的樣子之外,還實現了進度自動增長,點擊復位
所用到的知識
- 基礎的安卓view的繪製
- 基礎的安卓屬性動畫
怎麼做:
- 首先,這個進度條由三部分組成:1.淺灰背景,2.白色進度,3.中間一個圖片
- 所以就分別畫這三部分就可以了,從最下面開始畫
開始:
class KsFloatProgressView extends View {
private static final float STROKE_WIDTH = 2f;
private static final float BITMAP_WIDTH = 20f;
private Context mContext;
private int mWidth;
private int mHeight;
private Paint mBgPaint;
private Paint mProgressPaint;
private float mCircleRadius;
private Bitmap mCmBitmap;
private Rect mSrcRect;
private Rect mDestRect;
private Path mCirclePath;
private Path mStrokePath;
private int mBitmapMargin;
private int mBitmapWidth;
private int mStrokeWidth;
private OnCompleteListener mOnCompleteListener;
private float mProgress;
private ObjectAnimator mProgressAnim;
private int mAllProgress;
public KsFloatProgressView(Context context) {
this(context, null);
}
public KsFloatProgressView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KsFloatProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
//背景畫筆
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setStyle(Paint.Style.STROKE);
mBgPaint.setColor(Color.WHITE);
mBgPaint.setAlpha(36);
mBgPaint.setStrokeWidth(DimenUtils.dp2px(mContext, STROKE_WIDTH));
//進度畫筆
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setColor(Color.WHITE);
mProgressPaint.setStrokeWidth(DimenUtils.dp2px(mContext, STROKE_WIDTH));
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
//初始化圖片
mCmBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_ks_float_cm);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mCircleRadius = w / 2f - DimenUtils.dp2px(mContext, STROKE_WIDTH);
mSrcRect = new Rect(0, 0, mCmBitmap.getWidth(), mCmBitmap.getHeight());
mBitmapMargin = DimenUtils.dp2px(mContext, 5);
mBitmapWidth = DimenUtils.dp2px(mContext, BITMAP_WIDTH);
mStrokeWidth = DimenUtils.dp2px(mContext, STROKE_WIDTH);
mDestRect = new Rect(mBitmapMargin, mBitmapMargin, mBitmapMargin + mBitmapWidth, mBitmapMargin + mBitmapWidth);
mCirclePath = new Path();
mCirclePath.addCircle(mWidth / 2f, mHeight / 2f, mCircleRadius, Path.Direction.CW);
mStrokePath = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mStrokePath.addArc(mStrokeWidth, mStrokeWidth, mWidth - mStrokeWidth, mHeight - mStrokeWidth, -90, 90);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫背景
canvas.drawPath(mCirclePath, mBgPaint);
//畫圖片
canvas.drawBitmap(mCmBitmap, mSrcRect, mDestRect, mProgressPaint);
//畫進度
canvas.drawPath(mStrokePath, mProgressPaint);
}
public void setAllProgress(int allProgress){
mAllProgress = allProgress;
}
public void startProgress() {
post(new Runnable() {
@Override
public void run() {
mProgressAnim = ObjectAnimator.ofFloat(KsFloatProgressView.this, "RealProgress", 0, 1);
mProgressAnim.setDuration(mAllProgress*1000);
mProgressAnim.setInterpolator(new LinearInterpolator());
mProgressAnim.start();
mProgressAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mOnCompleteListener != null) {
mOnCompleteListener.onComplete();
}
}
});
}
});
}
private void setRealProgress(float progress) {
mProgress = progress;
float angle = progress * 360;
mStrokePath.reset();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mStrokePath.addArc(mStrokeWidth, mStrokeWidth, mWidth - mStrokeWidth, mHeight - mStrokeWidth, -90, angle);
}
invalidate();
}
public void resetProgress() {
if (mProgressAnim != null && mProgressAnim.isRunning()) {
mProgressAnim.cancel();
}
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "RealProgress", mProgress, 0);
anim.setDuration(300);
anim.setInterpolator(new AccelerateInterpolator());
anim.start();
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mProgressAnim.start();
}
});
}
public float getProgress() {
if (mProgressAnim == null) {
return 0;
}
return (float) mProgressAnim.getAnimatedValue();
}
public void setOnCompleteListener(OnCompleteListener listener) {
mOnCompleteListener = listener;
}
public interface OnCompleteListener {
void onComplete();
}
}
好的,現在就開始從上向下分析代碼
- 首先,因爲這個view不需要子view,所以直接繼承自view並重寫三個構造方法
- 構造方法裏調用了init方法,這個方法主要用來設置畫筆屬性以及初始化圖片資源
- 然後,在onSizeChanged方法裏獲取view的寬高,並由此計算出圓的半徑,根據獲得的view的寬高來設置圖片的寬高,以及位置
- 新建一個path,add一個circle用來畫圓背景,再新建另一個path,add一個圓弧用來畫進度
- 然後在ondraw方法裏畫背景,畫圖片,畫進度,當然一開始是沒有進度的
下面開始介紹讓進度動起來的方法
- 首先通過外部調用setAllProgress來設置總的轉一圈的時間(秒)
- 然後通過startProgress開始執行動畫,這個方法還是值得講一下的,忽略post,首先構建了一個ObjectAnimator,這個動畫的對象是this,沒錯,就是當前這個進度條控件,設置的屬性是RealProgress,這個屬性view裏是沒有的,所以我們待會兒需要寫一個設置這個屬性的方法,動畫值是從0~1的float
- 然後設置動畫時間,就是剛剛設置的allprogress,再設置一個線性插值器,然後開始動畫
- 下面這個setRealProgress就是在第二步屬性動畫設置的屬性“RealProgress”執行過程中會自動調用的方法,從這個方法的參數裏裏獲取進度(0~1的float),算出圓弧大小,再通知界面重繪
- resetProgress方法將動畫重置
總結
很簡單的一個自定義view,用到了自定義View中的path,以及部分屬性動畫只是,上面貼出的代碼就是全部代碼,可根據實際需求進行修改