最近在研究启航大佬的《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
}
})