自定义圆形进度条

本文基于《Android自定义控件开发入门与实践》一书,详细介绍了如何创建一个自定义的圆形进度条,包括初始化步骤:创建自定义View、定义属性、初始化、动画处理和控件大小自定义。在使用部分,讲解了通过布局文件和动态设置来初始化进度条,以及如何应用简单的和自定义的进度条动画。

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

最近在研究启航大佬的《Android自定义控件开发入门与实践》一书,内容详细有趣,忍不住写了一个自定义的圆形进度条

一、初始化

1.创建自定义View

public class RoundProgressBarView extends View {

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

    public RoundProgressBarView(Context context,AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RoundProgressBarView(Context context,AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBarView);
        init(typedArray);
    }
    
}

2.在res/values下新建attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundProgressBarView">
        <attr name="internal_color" format="color|reference"/>
        <attr name="internal_stroke_width" format="dimension"/>

        <attr name="external_color" format="color|reference"/>
        <attr name="external_stroke_width" format="dimension"/>
        <attr name="stroke_cap" >
            <enum  name="BUTT" value="0"/>
            <enum  name="ROUND" value="1"/>
            <enum  name="SQUARE" value="2"/>
        </attr>

        <attr name="max" format="integer"/>
        <attr name="schedule" format="integer"/>
        <attr name="text_color" format="color|reference"/>
        <attr name="text_size" format="dimension"/>
    </declare-styleable>
</resources>

3.自定义属性

首先要在根布局加上命名空间

xmlns:app="http://schemas.android.com/apk/res-auto"

然后在布局文件中使用attrs.xml文件中定义的属性

<com.huangmiao.kotlintest.view.RoundProgressBarView
    android:id="@+id/roundProgressBarView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:internal_color="@color/colorSilver"
    app:internal_stroke_width="5dp"
    app:external_color="@color/colorBlue"
    app:external_stroke_width="10dp"
    app:stroke_cap="ROUND"
    app:max="1000"
    app:schedule="600"
    app:text_color = "@color/colorBlue"
    app:text_size="30dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

4.自定义View初始化

//内层圈画笔
private Paint internalPaint;
public Paint getInternalPaint(){
    return internalPaint;
}

//内层圈画笔宽度
private float internalStrokeWidth = 10;
public void setInternalStrokeWidth(float internalStrokeWidth){
    internalPaint.setStrokeWidth(internalStrokeWidth);
    invalidate();
}

//内层圈画笔颜色
private int internalColor = ContextCompat.getColor(getContext().getApplicationContext(), R.color.colorSilver);
public void setInternalColor(@ColorRes int internalColor){
    internalPaint.setColor(ContextCompat.getColor(getContext().getApplicationContext(), internalColor));
    invalidate();
}

//外层圈画笔
private Paint externalPaint;
public Paint getExternalPaint(){
    return externalPaint;
}

//外层圈画笔宽度
private float externalStrokeWidth = 20;
public void setExternalStrokeWidth(float externalStrokeWidth){
    externalPaint.setStrokeWidth(externalStrokeWidth);
    invalidate();
}
//外层圈画笔颜色
private int externalColor = ContextCompat.getColor(getContext().getApplicationContext(), R.color.colorBlue);
public void setExternalColor(@ColorRes int externalColor){
    externalPaint.setColor(ContextCompat.getColor(getContext().getApplicationContext(), externalColor));
    invalidate();
}

//外层圈线帽样式
private Paint.Cap strokeCap;
public void setStrokeCap(Paint.Cap strokeCap){
    externalPaint.setStrokeCap(strokeCap);
    invalidate();
}

//文字画笔
private TextPaint textPaint;
public TextPaint getTextPaint(){
    return textPaint;
}

//文字颜色
private int textColor = Color.BLACK;
public void setTextColor(@ColorRes int textColor){
    textPaint.setColor(ContextCompat.getColor(getContext().getApplicationContext(), textColor));
    invalidate();
}

//文字大小
private float textSize = 16;
public void setTextSize(float textSize){
    textPaint.setTextSize(textSize);
    invalidate();
}

//圆形路径
private Path path;
//进度路径
private Path schedulePath;

private PathMeasure pathMeasure;

private void init(TypedArray typedArray){
    //禁止硬件加速
    setLayerType(LAYER_TYPE_SOFTWARE,null);

    //内层圈颜色
    internalColor = typedArray.getColor(R.styleable.RoundProgressBarView_internal_color,internalColor);
    //宽度
    internalStrokeWidth = typedArray.getDimension(R.styleable.RoundProgressBarView_internal_stroke_width,internalStrokeWidth);

    //外层圈颜色
    externalColor = typedArray.getColor(R.styleable.RoundProgressBarView_external_color,externalColor);
    //宽度
    externalStrokeWidth = typedArray.getDimension(R.styleable.RoundProgressBarView_external_stroke_width,externalStrokeWidth);
    //线帽
    int cap = typedArray.getInt(R.styleable.RoundProgressBarView_stroke_cap,1);
    switch (cap){
        case 0:
            strokeCap = Paint.Cap.BUTT;
            break;
        case 1:
            strokeCap = Paint.Cap.ROUND;
            break;
        case 2:
            strokeCap = Paint.Cap.SQUARE;
            break;
    }

    max = typedArray.getInteger(R.styleable.RoundProgressBarView_max,max);
    schedule = typedArray.getInteger(R.styleable.RoundProgressBarView_schedule,schedule);
    textColor = typedArray.getColor(R.styleable.RoundProgressBarView_text_color,textColor);
    textSize = typedArray.getDimension(R.styleable.RoundProgressBarView_text_size,textSize);

    //回收
    typedArray.recycle();

    textPaint = new TextPaint();
    textPaint.setColor(textColor);
    textPaint.setTextAlign(Paint.Align.CENTER);
    textPaint.setTextSize(textSize);
    textPaint.setAntiAlias(true);

    internalPaint = new Paint();
    internalPaint.setStrokeWidth(internalStrokeWidth);
    internalPaint.setColor(internalColor);
    //描边
    internalPaint.setStyle(Paint.Style.STROKE);
    //一般用于绘制不规则图形,如圆形、文字,显示更加圆滑
    internalPaint.setAntiAlias(true);

    externalPaint = new Paint();
    externalPaint.setStrokeWidth(externalStrokeWidth);
    externalPaint.setColor(externalColor);
    externalPaint.setStyle(Paint.Style.STROKE);
    //线帽样式
    externalPaint.setStrokeCap(strokeCap);
    externalPaint.setAntiAlias(true);

    path = new Path();
    schedulePath = new Path();
    pathMeasure = new PathMeasure();

}

5.进度条动画

//设置最大进度,setMax要在animatorCancel之前调用
private int max = 1;
public void setMax(int max){
    if (animator != null){
        animator.setIntValues(0,max);
    }
    this.max = max;
}

//设置当前进度
private int schedule = 0;
public void setSchedule(int schedule){
    this.schedule = schedule;
}

//动画
private ValueAnimator animator ;

/**
 * 设置进度条动画
 */
public void setAnimator(long duration){
    animator = ValueAnimator.ofInt(0, max);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //当前进度
            schedule = (int) animation.getAnimatedValue();
            invalidate();
        }
    });
    animator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationRepeat(Animator animation) {
            super.onAnimationRepeat(animation);
            if (isAnimatorCancel){
                animator.setIntValues(0,cancelSchedule);
                animator.setRepeatCount(ValueAnimator.RESTART);
            }
        }
    });
    animator.setDuration(duration);
    animator.start();
}

//是否关闭动画
private boolean isAnimatorCancel = false;

//停止动画时的位置
private int cancelSchedule;

/**
 * 结束动画,animatorCancel不能在setMax之前调用
 * @param cancelSchedule 停止动画时的位置
 */
public void animatorCancel(int cancelSchedule){
    isAnimatorCancel = true;
    if (cancelSchedule > max){
        this.cancelSchedule = max;
    }else {
        this.cancelSchedule = cancelSchedule;
    }
}

6.绘制

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    path.reset();
    float radius = (Math.min(getWidth(),getHeight()) - externalStrokeWidth) / 2f;
    path.addCircle(getWidth()/2f,getHeight()/2f,radius, Path.Direction.CW);
    canvas.drawPath(path,internalPaint);

    //中心点Y轴下移textPaint.ascent()/2f
    canvas.drawText(schedule+"",getWidth()/2f,(getHeight()- textPaint.ascent()/2f)/2,textPaint);

    //进度百分比
    float percentage;
    if (schedule > max){
        percentage = 1;
    }else {
        percentage = schedule / (max * 1f);
    }

    //清空路径
    schedulePath.reset();
    pathMeasure.setPath(path,false);
    //截取终点长度 = 百分比 * 总长度
    float stop = percentage * pathMeasure.getLength();
    //截取内层圆的一段路径并绘制
    pathMeasure.getSegment(0,stop,schedulePath,true);
    canvas.drawPath(schedulePath,externalPaint);
}

7.控件自定义大小

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
    int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
    int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
    int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

    setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth: 500, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight: 500);
}

二、使用

1.初始化

初始化提供了两种方式,方式一:布局文件中设置

app:internal_color="@color/colorSilver"//内层圈颜色
app:internal_stroke_width="5dp"//内层圈宽度
app:external_color="@color/colorBlue"//外层圈颜色
app:external_stroke_width="10dp"//外层圈宽度
app:stroke_cap="ROUND"//外层圈线帽样式
app:max="1000"//最大进度
app:schedule="600"//当前进度
app:text_color = "@color/colorBlue"//文字颜色
app:text_size="30dp"//文字大小

 

方式二:动态设置

//设置内圈颜色、宽度
roundProgressBarView.setInternalColor(R.color.colorBlue)
roundProgressBarView.setInternalStrokeWidth(20f)
//设置外圈颜色、宽度、线帽样式
roundProgressBarView.setExternalColor(R.color.colorSilver)
roundProgressBarView.setExternalStrokeWidth(10f)
roundProgressBarView.setStrokeCap(Paint.Cap.BUTT);
//设置文字颜色、大小
roundProgressBarView.setTextColor(R.color.colorTextBlack)
roundProgressBarView.setTextSize(20f)
//设置最大进度、当前进度
roundProgressBarView.setMax(1000)
roundProgressBarView.setSchedule(600)

 

如果有特殊需求,可以获取画笔自行设置

roundProgressBarView.getInternalPaint()
roundProgressBarView.getExternalPaint()
roundProgressBarView.getTextPaint()

当然设置完成后不要忘记提交

//主线程提交,效率高
roundProgressBarView.invalidate()
//任意线程提交
roundProgressBarView.postInvalidate()

 

2.进度条动画

简单使用动画

roundProgressBarView.setAnimator(1000)

也可以自定义动画

val animator = ValueAnimator.ofInt(0, 1000)
animator.addUpdateListener(ValueAnimator.AnimatorUpdateListener { animation ->
    roundProgressBarView.setSchedule(animation.animatedValue as Int)
    roundProgressBarView.invalidate()
})
animator.setDuration(1000)
animator.start()

运行到指定位置后结束动画

//animatorCancel不能在setMax之前调用
roundProgressBarView.animatorCancel(10000);

自定义结束动画

animator.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationRepeat(animation: Animator) {
        super.onAnimationRepeat(animation)
        animator.setIntValues(0, 500)
        animator.repeatCount = ValueAnimator.RESTART
    }
})

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值