手把手教你实现一个安卓环形进度条

背景:

最近做了个如图所示环形进度条,下面来记录一下实现过程,废话不多说,先上图
这是进度条
除了图中所示的样子之外,还实现了进度自动增长,点击复位

所用到的知识
  • 基础的安卓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,以及部分属性动画只是,上面贴出的代码就是全部代码,可根据实际需求进行修改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值