【Android 自定义 View】-->进度条合集

本文详细介绍并提供了四种自定义进度条的实现方法,包括圆形进度条、水平进度条、产品购买进度条和加载进度条。每种进度条都附带了详细的代码示例和属性配置说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不断学习,做更好的自己!💪

视频号优快云简书
欢迎打开微信,关注我的视频号:KevinDev点我点我

一个高级 Android 工程师除了要会使用 Google 原生的 View ,必须会根据实际项目的酷炫效果自己去写一些自定义 View 来解决问题,今天来给大家带来一些自定义进度条合集。

圆形进度条

1. 效果图

在这里插入图片描述

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleProgressBarView">
        <attr name="circleBgStrokeWidth" format="dimension" />
        <attr name="progressStrokeWidth" format="dimension" />
        <attr name="circleBgColor" format="color" />
        <attr name="progressColor" format="color" />
        <attr name="circleAnimationDuration" format="integer" />
        <attr name="isDrawCenterProgressText" format="boolean" />
        <attr name="centerProgressTextColor" format="color" />
        <attr name="centerProgressTextSize" format="dimension" />
    </declare-styleable>
</resources>

2. 核心代码 CircleProgressBarView.java

public class CircleProgressBarView extends View {
    private Context mContext;
    /**
     * 圆心x坐标
     */
    private float centerX;
    /**
     * 圆心y坐标
     */
    private float centerY;
    /**
     * 圆的半径
     */
    private float radius;

    /**
     * 进度
     */
    private float mProgress;

    /**
     * 当前进度
     */
    private float currentProgress;

    /**
     * 圆形进度条底色画笔
     */
    private Paint circleBgPaint;
    /**
     * 圆形进度条进度画笔
     */
    private Paint progressPaint;

    /**
     * 进度条背景颜色
     */
    private int circleBgColor = 0xFFe1e5e8;
    /**
     * 进度条颜色
     */
    private int progressColor = 0xFFf66b12;

    /**
     * 默认圆环的宽度
     */
    private int defaultStrokeWidth = 10;
    /**
     * 圆形背景画笔宽度
     */
    private int circleBgStrokeWidth = defaultStrokeWidth;
    /**
     * 圆形进度画笔宽度
     */
    private int progressStrokeWidth = defaultStrokeWidth;

    /**
     * 扇形所在矩形
     */
    private RectF rectF = new RectF();

    /**
     * 进度动画
     */
    private ValueAnimator progressAnimator;

    /**
     * 动画执行时间
     */
    private int duration = 1000;
    /**
     * 动画延时启动时间
     */
    private int startDelay = 500;

    private boolean isDrawCenterProgressText;
    private int centerProgressTextSize = 10;
    private int centerProgressTextColor = Color.BLACK;

    private Paint centerProgressTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private ProgressListener progressListener;

    public CircleProgressBarView(Context context) {
        this(context, null);
    }

    public CircleProgressBarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        getAttr(attrs);
        initPaint();
        initTextPaint();
    }

    private void getAttr(AttributeSet attrs) {
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgressBarView);
        circleBgStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_circleBgStrokeWidth,
                defaultStrokeWidth);
        progressStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_progressStrokeWidth,
                defaultStrokeWidth);
        circleBgColor = typedArray.getColor(R.styleable.CircleProgressBarView_circleBgColor, circleBgColor);
        progressColor = typedArray.getColor(R.styleable.CircleProgressBarView_progressColor, progressColor);
        duration = typedArray.getColor(R.styleable.CircleProgressBarView_circleAnimationDuration, duration);
        isDrawCenterProgressText = typedArray.getBoolean(R.styleable.CircleProgressBarView_isDrawCenterProgressText,
                false);
        centerProgressTextColor = typedArray.getColor(R.styleable.CircleProgressBarView_centerProgressTextColor,
                centerProgressTextColor);
        centerProgressTextSize = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_centerProgressTextSize,
                sp2px(centerProgressTextSize));

        typedArray.recycle();
    }

    private void initPaint() {
        circleBgPaint = getPaint(circleBgStrokeWidth, circleBgColor);
        progressPaint = getPaint(progressStrokeWidth, progressColor);
    }

    private Paint getPaint(int strokeWidth, int color) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(strokeWidth);
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStyle(Paint.Style.STROKE);
        return paint;
    }

    /**
     * 初始化文字画笔
     */
    private void initTextPaint() {
        centerProgressTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        centerProgressTextPaint.setTextSize(centerProgressTextSize);
        centerProgressTextPaint.setColor(centerProgressTextColor);
        centerProgressTextPaint.setTextAlign(Paint.Align.CENTER);
        centerProgressTextPaint.setAntiAlias(true);
    }

    private void initAnimation() {
        progressAnimator = ValueAnimator.ofFloat(0, mProgress);
        progressAnimator.setDuration(duration);
        progressAnimator.setStartDelay(startDelay);
        progressAnimator.setInterpolator(new LinearInterpolator());
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                mProgress = value;
                currentProgress = value * 360 / 100;

                if (progressListener != null) {
                    progressListener.currentProgressListener(roundTwo(value));
                }
                invalidate();
            }
        });
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        radius = Math.min(w, h) / 2 - Math.max(circleBgStrokeWidth, progressStrokeWidth);
        rectF.set(centerX - radius,
                centerY - radius,
                centerX + radius,
                centerY + radius);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, circleBgPaint);
        canvas.drawArc(rectF, 90, currentProgress, false, progressPaint);
        if (isDrawCenterProgressText) {
            drawCenterProgressText(canvas, (int) mProgress + "%");
        }
    }

    private void drawCenterProgressText(Canvas canvas, String currentProgress) {
        Paint.FontMetricsInt fontMetrics = centerProgressTextPaint.getFontMetricsInt();
        int baseline = (int) ((rectF.bottom + rectF.top - fontMetrics.bottom - fontMetrics.top) / 2);
        //文字绘制到整个布局的中心位置
        canvas.drawText(currentProgress, rectF.centerX(), baseline, centerProgressTextPaint);
    }


    public void startProgressAnimation() {
        progressAnimator.start();
    }

    public void pauseProgressAnimation() {
        progressAnimator.pause();
    }

    public void resumeProgressAnimation() {
        progressAnimator.resume();
    }

    public void stopProgressAnimation() {
        progressAnimator.end();
    }


    /**
     * 传入一个进度值,从0到progress动画变化
     *
     * @param progress
     * @return
     */
    public CircleProgressBarView setProgressWithAnimation(float progress) {
        mProgress = progress;
        initAnimation();
        return this;
    }

    /**
     * 实时进度,适用于下载进度回调时候之类的场景
     *
     * @param progress
     * @return
     */
    public CircleProgressBarView setCurrentProgress(float progress) {
        mProgress = progress;
        currentProgress = progress * 360 / 100;
        invalidate();
        return this;
    }


    public interface ProgressListener {
        void currentProgressListener(float currentProgress);
    }

    public CircleProgressBarView setProgressListener(ProgressListener listener) {
        progressListener = listener;
        return this;
    }

    /**
     * 将一个小数四舍五入,保留两位小数返回
     *
     * @param originNum
     * @return
     */
    public static float roundTwo(float originNum) {
        return (float) (Math.round(originNum * 10) / 10.00);
    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     *
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());
    }
}

3. 使用

<com.hkt.demo.widget.CircleProgressBarView
        android:id="@+id/circle_progress_view"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:layout_marginTop="20dp"
        app:centerProgressTextColor="@color/text_color"
        app:centerProgressTextSize="20sp"
        app:circleBgStrokeWidth="10dp"
        app:isDrawCenterProgressText="true"
        app:progressStrokeWidth="10dp"/>
public class MainActivity extends BaseActivity implements 
        CircleProgressBarView.ProgressListener {

        @BindView(R.id.circle_progress_view)
        CircleProgressBarView mCircleProgressBarView;

        @BindView(R.id.tv_process)
        TextView mProgressValue;

        @Override
        protected int getLayoutId() {
            return R.layout.activity_main;
        }

        @Override
        protected void initView() {
            mCircleProgressBarView.setProgressWithAnimation(60f);
            mCircleProgressBarView.setProgressListener(this);
            mCircleProgressBarView.startProgressAnimation();
        }

        @Override
        protected void initData() {

        }

        @Override
        public void currentProgressListener(float currentProgress) {
            mProgressValue.setText("当前进度:" + currentProgress);
        }

    @Override
    protected void onResume() {
        super.onResume();
        mCircleProgressBarView.resumeProgressAnimation();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mCircleProgressBarView.pauseProgressAnimation();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCircleProgressBarView.stopProgressAnimation();
    }

}

水平进度条

1. 效果图

在这里插入图片描述

2. 核心代码 HorizontalProgressBar.java

public class HorizontalProgressBar extends View {
    private Paint bgPaint;
    private Paint progressPaint;

    private Paint tipPaint;
    private Paint textPaint;

    private int mWidth;
    private int mHeight;
    private int mViewHeight;
    /**
     * 进度
     */
    private float mProgress;

    /**
     * 当前进度
     */
    private float currentProgress;

    /**
     * 进度动画
     */
    private ValueAnimator progressAnimator;

    /**
     * 动画执行时间
     */
    private int duration = 1000;
    /**
     * 动画延时启动时间
     */
    private int startDelay = 500;

    /**
     * 进度条画笔的宽度
     */
    private int progressPaintWidth;

    /**
     * 百分比提示框画笔的宽度
     */
    private int tipPaintWidth;

    /**
     * 百分比提示框的高度
     */
    private int tipHeight;

    /**
     * 百分比提示框的宽度
     */
    private int tipWidth;

    /**
     * 画三角形的path
     */
    private Path path = new Path();
    /**
     * 三角形的高
     */
    private int triangleHeight;
    /**
     * 进度条距离提示框的高度
     */
    private int progressMarginTop;

    /**
     * 进度移动的距离
     */
    private float moveDis;

    private Rect textRect = new Rect();
    private String textString = "0";
    /**
     * 百分比文字字体大小
     */
    private int textPaintSize;

    /**
     * 进度条背景颜色
     */
    private int bgColor = 0xFFe1e5e8;
    /**
     * 进度条颜色
     */
    private int progressColor = 0xFFf66b12;

    /**
     * 绘制提示框的矩形
     */
    private RectF rectF = new RectF();

    /**
     * 圆角矩形的圆角半径
     */
    private int roundRectRadius;

    /**
     * 进度监听回调
     */
    private ProgressListener progressListener;

    public HorizontalProgressBar(Context context) {
        super(context);
    }

    public HorizontalProgressBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
        initPaint();
    }

    /**
     * 初始化画笔宽度及view大小
     */
    private void init() {
        progressPaintWidth = dp2px(4);
        tipHeight = dp2px(15);
        tipWidth = dp2px(30);
        tipPaintWidth = dp2px(1);
        triangleHeight = dp2px(3);
        roundRectRadius = dp2px(2);
        textPaintSize = sp2px(10);
        progressMarginTop = dp2px(8);

        //view真实的高度
        mViewHeight = tipHeight + tipPaintWidth + triangleHeight + progressPaintWidth + progressMarginTop;
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        bgPaint = getPaint(progressPaintWidth, bgColor, Paint.Style.STROKE);
        progressPaint = getPaint(progressPaintWidth, progressColor, Paint.Style.STROKE);
        tipPaint = getPaint(tipPaintWidth, progressColor, Paint.Style.FILL);

        initTextPaint();
    }

    /**
     * 初始化文字画笔
     */
    private void initTextPaint() {
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(textPaintSize);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setAntiAlias(true);
    }

    /**
     * 统一处理paint
     *
     * @param strokeWidth
     * @param color
     * @param style
     * @return
     */
    private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(strokeWidth);
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStyle(style);
        return paint;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(measureWidth(widthMode, width), measureHeight(heightMode, height));
    }

    /**
     * 测量宽度
     *
     * @param mode
     * @param width
     * @return
     */
    private int measureWidth(int mode, int width) {
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                break;
            case MeasureSpec.EXACTLY:
                mWidth = width;
                break;
        }
        return mWidth;
    }

    /**
     * 测量高度
     *
     * @param mode
     * @param height
     * @return
     */
    private int measureHeight(int mode, int height) {
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                mHeight = mViewHeight;
                break;
            case MeasureSpec.EXACTLY:
                mHeight = height;
                break;
        }
        return mHeight;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawLine(getPaddingLeft(),
                tipHeight + progressMarginTop,
                getWidth(),
                tipHeight + progressMarginTop,
                bgPaint);

        canvas.drawLine(getPaddingLeft(),
                tipHeight + progressMarginTop,
                currentProgress,
                tipHeight + progressMarginTop,
                progressPaint);

        drawTipView(canvas);
        drawText(canvas, textString);

    }

    /**
     * 绘制进度上边提示百分比的view
     *
     * @param canvas
     */
    private void drawTipView(Canvas canvas) {
        drawRoundRect(canvas);
        drawTriangle(canvas);
    }


    /**
     * 绘制圆角矩形
     *
     * @param canvas
     */
    private void drawRoundRect(Canvas canvas) {
        rectF.set(moveDis, 0, tipWidth + moveDis, tipHeight);
        canvas.drawRoundRect(rectF, roundRectRadius, roundRectRadius, tipPaint);
    }

    /**
     * 绘制三角形
     *
     * @param canvas
     */
    private void drawTriangle(Canvas canvas) {
        path.moveTo(tipWidth / 2 - triangleHeight + moveDis, tipHeight);
        path.lineTo(tipWidth / 2 + moveDis, tipHeight + triangleHeight);
        path.lineTo(tipWidth / 2 + triangleHeight + moveDis, tipHeight);
        canvas.drawPath(path, tipPaint);
        path.reset();

    }

    /**
     * 绘制文字
     *
     * @param canvas 画布
     */
    private void drawText(Canvas canvas, String textString) {
        textRect.left = (int) moveDis;
        textRect.top = 0;
        textRect.right = (int) (tipWidth + moveDis);
        textRect.bottom = tipHeight;
        Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
        int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        //文字绘制到整个布局的中心位置
        canvas.drawText(textString + "%", textRect.centerX(), baseline, textPaint);
    }

    /**
     * 进度移动动画  通过插值的方式改变移动的距离
     */
    private void initAnimation() {
        progressAnimator = ValueAnimator.ofFloat(0, mProgress);
        progressAnimator.setDuration(duration);
        progressAnimator.setStartDelay(startDelay);
        progressAnimator.setInterpolator(new LinearInterpolator());
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                //进度数值只显示整数,我们自己的需求,可以忽略
                textString = formatNum(format2Int(value));
                //把当前百分比进度转化成view宽度对应的比例
                currentProgress = value * mWidth / 100;
                //进度回调方法
                if (progressListener != null) {
                    progressListener.currentProgressListener(value);
                }
                //移动百分比提示框,只有当前进度到提示框中间位置之后开始移动,
                //当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动
                //moveDis是tip框移动的距离
                if (currentProgress >= (tipWidth / 2) &&
                        currentProgress <= (mWidth - tipWidth / 2)) {
                    moveDis = currentProgress - tipWidth / 2;
                }
                invalidate();
                setCurrentProgress(value);
            }
        });
        progressAnimator.start();
    }


    /**
     * 设置进度条带动画效果
     *
     * @param progress
     * @return
     */
    public HorizontalProgressBar setProgressWithAnimation(float progress) {
        mProgress = progress;
        initAnimation();
        return this;
    }

    /**
     * 实时显示进度
     *
     * @param progress
     * @return
     */
    public HorizontalProgressBar setCurrentProgress(float progress) {
        mProgress = progress;
        currentProgress = progress * mWidth / 100;
        textString = formatNum(format2Int(progress));

        //移动百分比提示框,只有当前进度到提示框中间位置之后开始移动,
        //当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动
        //moveDis是tip框移动的距离
        if (currentProgress >= (tipWidth / 2) &&
                currentProgress <= (mWidth - tipWidth / 2)) {
            moveDis = currentProgress - tipWidth / 2;
        }

        invalidate();
        return this;
    }

    /**
     * 开启动画
     */
    public void startProgressAnimation() {
        if (progressAnimator != null &&
                !progressAnimator.isRunning() &&
                !progressAnimator.isStarted())
            progressAnimator.start();
    }

    /**
     * 暂停动画
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void pauseProgressAnimation() {
        if (progressAnimator != null) {
            progressAnimator.pause();
        }
    }

    /**
     * 恢复动画
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void resumeProgressAnimation() {
        if (progressAnimator != null)
            progressAnimator.resume();
    }

    /**
     * 停止动画
     */
    public void stopProgressAnimation() {
        if (progressAnimator != null) {
            progressAnimator.end();
        }
    }

    /**
     * 回调接口
     */
    public interface ProgressListener {
        void currentProgressListener(float currentProgress);
    }

    /**
     * 回调监听事件
     *
     * @param listener
     * @return
     */
    public HorizontalProgressBar setProgressListener(ProgressListener listener) {
        progressListener = listener;
        return this;
    }

    /**
     * 格式化数字(保留两位小数)
     *
     * @param money
     * @return
     */
    public static String formatNumTwo(double money) {
        DecimalFormat format = new DecimalFormat("0.00");
        return format.format(money);
    }

    /**
     * 格式化数字(保留一位小数)
     *
     * @param money
     * @return
     */
    public static String formatNum(int money) {
        DecimalFormat format = new DecimalFormat("0");
        return format.format(money);
    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     *
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());

    }

    public static int format2Int(double i) {
        return (int) i;
    }
}

3. 使用

<com.hkt.demo.widget.HorizontalProgressBar
        android:id="@+id/hpb_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"/>
public class MainActivity extends BaseActivity implements HorizontalProgressBar.ProgressListener {
    @BindView(R.id.hpb_progress)
    HorizontalProgressBar mProgressBar;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initView() {
        mProgressBar.setProgressWithAnimation(30f);
        mProgressBar.setProgressListener(this);
        mProgressBar.startProgressAnimation();
    }

    @Override
    protected void initData() {

    }

    @OnClick(R.id.btn_down)
    public void clicked(View view) {
        mProgressBar.setProgressWithAnimation(100f);
    }

    @Override
    public void currentProgressListener(float currentProgress) {

    }

}

产品购买进度条

1. 效果图

在这里插入图片描述

2. 核心代码 ProductProgressBar.java

public class ProductProgressBar extends View {
    private Paint bgPaint;
    private Paint progressPaint;

    private Paint textPaint;

    private int mWidth;
    private int mHeight;

    private int mViewHeight;
    /**
     * 进度
     */
    private float mProgress;
    //描述文字的高度
    private float textHeight;
    //描述文字的高度
    private float textWidth;

    /**
     * 当前进度
     */
    private float currentProgress;

    /**
     * 进度动画
     */
    private ValueAnimator progressAnimator;

    /**
     * 动画执行时间
     */
    private int duration = 1000;
    /**
     * 动画延时启动时间
     */
    private int startDelay = 500;

    /**
     * 进度条画笔的宽度
     */
    private int progressPaintWidth;

    private int progressHeight;

    /**
     * 进度条距离提示框的高度
     */
    private int progressMarginTop;

    /**
     * 进度移动的距离
     */
    private float moveDis;

    private Rect textRect = new Rect();

    private String textString = "已售0%";
    /**
     * 百分比文字字体大小
     */
    private int textPaintSize;

    /**
     * 进度条背景颜色
     */
    private int bgColor = 0xFFeaeef0;
    /**
     * 进度条颜色
     */
    private int progressColor = 0xFFf66b12;


    private RectF bgRectF = new RectF();
    private RectF progressRectF = new RectF();

    /**
     * 圆角矩形的圆角半径
     */
    private int roundRectRadius;

    /**
     * 进度监听回调
     */
    private ProgressListener progressListener;

    public ProductProgressBar(Context context) {
        this(context, null);
    }

    public ProductProgressBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ProductProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        initPaint();
        initTextPaint();
    }

    /**
     * 初始化画笔宽度及view大小
     */
    private void init() {
        progressPaintWidth = dp2px(1);
        progressHeight = dp2px(3);
        roundRectRadius = dp2px(3);
        textPaintSize = sp2px(10);
        textHeight = dp2px(10);
        progressMarginTop = dp2px(4);

        //view真实的高度
        mViewHeight = (int) (textHeight + progressMarginTop + progressPaintWidth * 2 + progressHeight);
    }


    private void initPaint() {
        bgPaint = getPaint(progressPaintWidth, bgColor, Paint.Style.FILL);
        progressPaint = getPaint(progressPaintWidth, progressColor, Paint.Style.FILL);
    }

    /**
     * 初始化文字画笔
     */
    private void initTextPaint() {
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(textPaintSize);
        textPaint.setColor(progressColor);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setAntiAlias(true);
    }


    /**
     * 统一处理paint
     *
     * @param strokeWidth 画笔宽度
     * @param color       颜色
     * @param style       风格
     * @return paint
     */
    private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(strokeWidth);
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setStyle(style);
        return paint;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(measureWidth(widthMode, width), measureHeight(heightMode, height));
    }

    /**
     * 测量宽度
     *
     * @param mode
     * @param width
     * @return
     */
    private int measureWidth(int mode, int width) {
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                break;
            case MeasureSpec.EXACTLY:
                mWidth = width;
                break;
        }
        return mWidth;
    }

    /**
     * 测量高度
     *
     * @param mode
     * @param height
     * @return
     */
    private int measureHeight(int mode, int height) {
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                mHeight = mViewHeight;
                break;
            case MeasureSpec.EXACTLY:
                mHeight = height;
                break;
        }
        return mHeight;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘制文字
        drawText(canvas, textString);
        //背景
        drawBgProgress(canvas);
        //进度条
        drawProgress(canvas);
    }

    private void drawBgProgress(Canvas canvas) {
        bgRectF.left = 0;
        bgRectF.top = textHeight + progressMarginTop;
        bgRectF.right = this.getMeasuredWidth();
        bgRectF.bottom = bgRectF.top + progressHeight;
        canvas.drawRoundRect(bgRectF, roundRectRadius, roundRectRadius, bgPaint);
    }

    private void drawProgress(Canvas canvas) {
        progressRectF.left = 0;
        progressRectF.top = textHeight + progressMarginTop;
        progressRectF.right = currentProgress;
        progressRectF.bottom = progressRectF.top + progressHeight;
        canvas.drawRoundRect(progressRectF, roundRectRadius, roundRectRadius, progressPaint);
    }


    /**
     * 绘制文字
     *
     * @param canvas 画布
     */
    private void drawText(Canvas canvas, String textString) {
        textRect.left = (int) moveDis;
        textRect.top = 0;
        textRect.right = (int) (textPaint.measureText(textString) + moveDis);
        textRect.bottom = (int) textHeight;
        Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
        int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        //文字绘制到整个布局的中心位置
        canvas.drawText(textString, textRect.centerX(), baseline, textPaint);
    }


    /**
     * 进度移动动画  通过插值的方式改变移动的距离
     */
    private void initAnimation() {
        progressAnimator = ValueAnimator.ofFloat(0, mProgress);
        progressAnimator.setDuration(duration);
        progressAnimator.setStartDelay(startDelay);
        progressAnimator.setInterpolator(new LinearInterpolator());
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                textString = "已售" + formatNum((int) value) + "%";
                textWidth = textPaint.measureText(textString);
                currentProgress = value * mWidth / 100;
                if (progressListener != null) {
                    progressListener.currentProgressListener(value);
                }
                //移动百分比提示框,只有当前进度到提示框中间位置之后开始移动,当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动
                if (currentProgress >= textWidth && currentProgress <= mWidth) {
                    moveDis = currentProgress - textWidth;
                }
                invalidate();
            }
        });
        if (!progressAnimator.isStarted()) {
            progressAnimator.start();
        }
    }

    /**
     * 回调接口
     */
    public interface ProgressListener {
        void currentProgressListener(float currentProgress);
    }

    /**
     * 回调监听事件
     *
     * @param listener
     * @return
     */
    public ProductProgressBar setProgressListener(ProgressListener listener) {
        progressListener = listener;
        return this;
    }

    public ProductProgressBar setProgress(float progress) {
        mProgress = progress;
        initAnimation();
        return this;
    }

    /**
     * 格式化数字(保留一位小数)
     *
     * @param money
     * @return
     */
    public static String formatNum(int money) {
        DecimalFormat format = new DecimalFormat("0");
        return format.format(money);
    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     *
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());
    }
}

3. 使用

<com.hkt.demo.widget.ProductProgressBar
        android:id="@+id/ppb_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"/>
public class MainActivity extends BaseActivity implements ProductProgressBar.ProgressListener {
    @BindView(R.id.ppb_progress)
    ProductProgressBar mProgressBar;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initView() {
        mProgressBar.setProgress(60f);
        mProgressBar.setProgressListener(this);
    }

    @Override
    protected void initData() {

    }

    @Override
    public void currentProgressListener(float currentProgress) {

    }
}

加载进度条

1. 效果图

在这里插入图片描述

2. 核心代码

  • LoadingLineView.java
public class LoadingLineView extends View {
    private int mWidth;
    private int mHeight;

    /**
     * 动画起点x坐标
     */
    private int centerX;
    /**
     * 动画起点y坐标
     */
    private int centerY;

    /**
     * 偏移距离
     */
    private float dis;

    /**
     * view真实高度
     */
    private int mViewHeight;

    /**
     * 背景色画笔
     */
    private Paint bgPaint;
    /**
     * loading画笔
     */
    private Paint loadingPaint;

    /**
     * 画笔宽度(等于vie高度)
     */
    private int paintWidth;

    /**
     * 底色
     */
    private int bgColor = 0xFFe1e5e8;

    /**
     * loading颜色
     */
    private int loadingColor = 0xFFf66b12;


    /**
     * 动画
     */
    private ValueAnimator loadingAnimator;
    /**
     * 动画执行时间
     */
    private int duration = 800;
    /**
     * 动画延时启动时间
     */
    private int startDelay = 0;

    /**
     * 是否停止动画(恢复初始状态)
     */
    private boolean isStopAnimation = false;

    public LoadingLineView(Context context) {
        this(context, null);
    }

    public LoadingLineView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadingLineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getAtt(attrs);
        init();
    }

    /**
     * 想实现颜色等参数可配置,在这里实现配置即可,笔者就不多写了
     *
     * @param attrs attrs
     */
    private void getAtt(AttributeSet attrs) {

    }

    private void init() {
        paintWidth = dp2px(2);
        mViewHeight = paintWidth;
        bgPaint = getPaint(paintWidth, bgColor, Paint.Style.FILL);
        loadingPaint = getPaint(paintWidth, loadingColor, Paint.Style.FILL);
    }


    /**
     * 统一处理paint
     *
     * @param strokeWidth 画笔宽度
     * @param color       颜色
     * @param style       风格
     * @return paint
     */
    private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(strokeWidth);
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setStyle(style);
        return paint;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mWidth = w;
        mHeight = h;

        centerX = w / 2;
        centerY = h / 2;

        initLoadingAnimation();
        startLoading();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(measureWidth(widthMode, width), measureHeight(heightMode, height));
    }

    /**
     * 测量宽度
     *
     * @param mode
     * @param width
     * @return
     */
    private int measureWidth(int mode, int width) {
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                break;
            case MeasureSpec.EXACTLY:
                mWidth = width;
                break;
        }
        return mWidth;
    }

    /**
     * 测量高度
     *
     * @param mode
     * @param height
     * @return
     */
    private int measureHeight(int mode, int height) {
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                mHeight = mViewHeight;
                break;
            case MeasureSpec.EXACTLY:
                mHeight = height;
                break;
        }
        return mHeight;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 画底色
         */
        canvas.drawLine(0, centerY, mWidth, centerY, bgPaint);

        if (!isStopAnimation) {
            /**
             * loading向左扩散
             */
            canvas.drawLine(centerX, centerY, centerX - dis, centerY, loadingPaint);
            /**
             * loading向右扩散
             */
            canvas.drawLine(centerX, centerY, centerX + dis, centerY, loadingPaint);
        }

    }

    /**
     * 初始化动画
     */
    private void initLoadingAnimation() {
        final float loadingMoveDistance = mWidth / 2;
        loadingAnimator = ValueAnimator.ofFloat(0, loadingMoveDistance);
        loadingAnimator.setDuration(duration);
        loadingAnimator.setStartDelay(startDelay);
        loadingAnimator.setRepeatCount(ValueAnimator.INFINITE);
        loadingAnimator.setInterpolator(new LinearInterpolator());
        loadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                dis = value;
                if (value <= loadingMoveDistance / 2) {
                    loadingPaint.setAlpha((int) ((255 * value) * 2 / loadingMoveDistance));
                } else {
                    loadingPaint.setAlpha((int) (255 - (255 * value) * 2 / loadingMoveDistance));
                }
                invalidate();
            }
        });
    }

    /**
     * 开启动画
     */
    public void startLoading() {
        if (loadingAnimator != null) {
            loadingAnimator.start();
        }
        isStopAnimation = false;
    }

    /**
     * 结束动画
     */
    public void stopLoading() {
        if (loadingAnimator != null) {
            loadingAnimator.cancel();
        }
        isStopAnimation = true;
        invalidate();
    }


    /**
     * dp 2 px
     *
     * @param dpVal dp
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }
}
  • LoadingView.java
public class LoadingView extends View {
    private int mWidth;
    private int mHeight;

    private int centerX;
    private int centerY;

    private float radius;

    private Paint bgCirclePaint;
    private Paint arcPaint;
    private Paint okPaint;

    private int paintWidth;

    /**
     * 绘制圆弧区域
     */
    private RectF rectF = new RectF();

    /**
     * 默认圆背景颜色
     */
    private int bgColor = 0xFFe1e5e8;
    /**
     * 圆弧进度条颜色
     */
    private int progressColor = 0xFFf66b12;

    /**
     * 动画执行时间
     */
    private int duration = 800;
    /**
     * 动画延时启动时间
     */
    private int startDelay = 0;

    /**
     * 圆弧开始偏移角度
     */
    private float startAngle = 0;
    /**
     * 偏移长度
     */
    private float sweepAngle = 20;

    /**
     * 圆弧转圈
     */
    private ValueAnimator animatorDrawLoading;

    /**
     *
     */
    private ValueAnimator animatorDrawArcToCircle;
    /**
     * 绘制对勾(√)的动画
     */
    private ValueAnimator animatorDrawOk;
    /**
     * 路径--用来获取对勾的路径
     */
    private Path path = new Path();
    /**
     * 取路径的长度
     */
    private PathMeasure pathMeasure;
    /**
     * 对路径处理实现绘制动画效果
     */
    private PathEffect effect;


    /**
     * 是否开始绘制对勾
     */
    private boolean startDrawOk = false;

    /**
     * 动画集
     */
    private AnimatorSet animatorSet = new AnimatorSet();


    /**
     * 点击事件及动画事件2完成回调
     */
    private LoadingViewListener loadingViewListener;

    public void setLoadingViewListener(LoadingViewListener loadingViewListener) {
        this.loadingViewListener = loadingViewListener;
    }


    public LoadingView(Context context) {
        this(context, null);
    }

    public LoadingView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paintWidth = dp2px(3);
        initPaint();
        initAnimatorSet();
    }

    private void initPaint() {
        bgCirclePaint = getPaint(paintWidth, bgColor, Paint.Style.STROKE);
        arcPaint = getPaint(paintWidth, progressColor, Paint.Style.STROKE);
        okPaint = getPaint(paintWidth, progressColor, Paint.Style.STROKE);
    }

    /**
     * 统一处理paint
     *
     * @param strokeWidth 画笔宽度
     * @param color       颜色
     * @param style       风格
     * @return paint
     */
    private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(strokeWidth);
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setStyle(style);
        return paint;
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mWidth = w;
        mHeight = h;

        centerX = w / 2;
        centerY = h / 2;

        radius = Math.min(w, h) / 2 - paintWidth;

        rectF.left = centerX - radius;
        rectF.top = centerY - radius;
        rectF.right = centerX + radius;
        rectF.bottom = centerY + radius;

        initOk();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, bgCirclePaint);
        canvas.drawArc(rectF, startAngle, sweepAngle, false, arcPaint);
        if (startDrawOk) {
            canvas.drawPath(path, okPaint);
        }
    }

    /**
     * 绘制对勾
     */
    private void initOk() {
        //对勾的路径
        path.moveTo(mWidth / 8 * 3, mHeight / 2);
        path.lineTo(mWidth / 2, mHeight / 5 * 3);
        path.lineTo(mWidth / 3 * 2, mHeight / 5 * 2);

        pathMeasure = new PathMeasure(path, true);
    }

    /**
     * 初始化弧度转圈的动画
     */
    private void initLoadingAnimation() {
        animatorDrawLoading = ValueAnimator.ofFloat(0, 360);
        animatorDrawLoading.setDuration(duration);
        animatorDrawLoading.setStartDelay(startDelay);
        animatorDrawLoading.setRepeatCount(2);
        animatorDrawLoading.setInterpolator(new LinearInterpolator());
        animatorDrawLoading.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                startAngle = value;
                if (startAngle <= 180) {
                    sweepAngle = sweepAngle + 5;
                } else {
                    sweepAngle = sweepAngle - 5;
                }
                invalidate();
            }
        });
    }

    /**
     * 初始化弧度变圆动画
     */
    private void initArcToCircleAnimation() {
        animatorDrawArcToCircle = ValueAnimator.ofFloat(sweepAngle, 360);
        animatorDrawArcToCircle.setDuration(duration);
        animatorDrawArcToCircle.setStartDelay(0);
        animatorDrawArcToCircle.setInterpolator(new LinearInterpolator());
        animatorDrawArcToCircle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                sweepAngle = value;
                invalidate();
            }
        });
    }


    /**
     * 绘制对勾的动画
     */
    private void initOkAnimation() {
        animatorDrawOk = ValueAnimator.ofFloat(1, 0);
        animatorDrawOk.setDuration(duration);
        animatorDrawOk.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                startDrawOk = true;
                float value = (Float) animation.getAnimatedValue();

                effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
                okPaint.setPathEffect(effect);
                invalidate();
            }
        });
    }

    /**
     * 初始化所有动画
     */
    private void initAnimatorSet() {
        initLoadingAnimation();
        initArcToCircleAnimation();
        initOkAnimation();
        animatorSet
                .play(animatorDrawArcToCircle)
                .before(animatorDrawOk)
                .after(animatorDrawLoading);

        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (loadingViewListener != null) {
                    loadingViewListener.animationFinish();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

    }

    /**
     * 传入一个进度值,从0到progress动画变化
     */
    public LoadingView startAnimation() {
        startDrawOk = false;
        startAngle = 0;
        sweepAngle = 20;
        path = new Path();
        initOk();
        animatorSet.cancel();
        animatorSet.start();
        return this;
    }

    /**
     * 接口回调
     */
    public interface LoadingViewListener {
        /**
         * 动画完成回调
         */
        void animationFinish();
    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }
}

3. 使用

<com.hkt.demo.widget.LoadingLineView
        android:id="@+id/llv_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"/>

    <com.hkt.demo.widget.LoadingView
        android:id="@+id/loading_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center"/>

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:text="启动动画"/>
public class MainActivity extends BaseActivity {
    @BindView(R.id.llv_progress)
    LoadingLineView mLoadingLineView;

    @BindView(R.id.loading_view)
    LoadingView mLoadingView;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initView() {
        mLoadingView.startAnimation();
    }

    @Override
    protected void initData() {

    }

    @OnClick(R.id.btn_start)
    public void start() {
        mLoadingLineView.startLoading();
        mLoadingView.startAnimation();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin-Dev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值