背景:
最近做了个如图所示环形进度条,下面来记录一下实现过程,废话不多说,先上图
除了图中所示的样子之外,还实现了进度自动增长,点击复位
所用到的知识
- 基础的安卓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,以及部分属性动画只是,上面贴出的代码就是全部代码,可根据实际需求进行修改