注册

Android 自定义View 之 饼状进度条

前言


  前面写了圆环进度条,这次我们来写一个饼状进度条,首先看一下效果图:


在这里插入图片描述


正文


  效果图感觉怎么样呢?下面我们来实现这个自定义View,依然是写在EasyView这个项目中,这是一个自定义View库,我会把自己写的自定义View都放在里面,文中如果代码不是很全的话,你可以找到文章最后的源码去查看,话不多说,我们开始吧。


一、XML样式


  根据上面的效果图,我们首先来确定XML中的属性样式,在attrs.xml中添加如下代码:

	<!--饼状进度条-->
<declare-styleable name="PieProgressBar">
<!--半径-->
<attr name="radius" />
<!--最大进度-->
<attr name="maxProgress" />
<!--当前进度-->
<attr name="progress" />
<!--进度条进度颜色-->
<attr name="progressbarColor" />
<!--进度条描边宽度-->
<attr name="strokeWidth"/>
<!--进度是否渐变-->
<attr name="gradient" />
<!--渐变颜色数组-->
<attr name="gradientColorArray" />
<!--自定义开始角度 0 ,90,180,270-->
<attr name="customAngle">
<enum name="right" value="0" />
<enum name="bottom" value="90" />
<enum name="left" value="180" />
<enum name="top" value="270" />
</attr>
</declare-styleable>

  这里的公共属性我就抽离了出来,因为之前写过圆环进度条,有一些属性是可以通用的,并且我在饼状进度条中增加了开始的角度,之前是默认是从0°开始,现在可以根据属性设置开始的角度,并且我增加了渐变颜色。


二、构造方法


  现在属性样式已经有了,下一步就是写自定义View的构造方法了,在com.easy.view包下新建一个PieProgressBar 类,里面的代码如下所示:

public class PieProgressBar extends View {

/**
* 半径
*/
private int mRadius;
/**
* 进度条宽度
*/
private int mStrokeWidth;
/**
* 进度条进度颜色
*/
private int mProgressColor;
/**
* 开始角度
*/
private int mStartAngle = 0;

/**
* 当前角度
*/
private float mCurrentAngle = 0;
/**
* 结束角度
*/
private int mEndAngle = 360;
/**
* 最大进度
*/
private float mMaxProgress;
/**
* 当前进度
*/
private float mCurrentProgress;
/**
* 是否渐变
*/
private boolean isGradient;
/**
* 渐变颜色数组
*/
private int[] colorArray;
/**
* 动画的执行时长
*/
private long mDuration = 1000;
/**
* 是否执行动画
*/
private boolean isAnimation = false;

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

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

public PieProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PieProgressBar);
mRadius = array.getDimensionPixelSize(R.styleable.PieProgressBar_radius, 80);
mStrokeWidth = array.getDimensionPixelSize(R.styleable.PieProgressBar_strokeWidth, 8);
mProgressColor = array.getColor(R.styleable.PieProgressBar_progressbarColor, ContextCompat.getColor(context, R.color.tx_default_color));
mMaxProgress = array.getInt(R.styleable.PieProgressBar_maxProgress, 100);
mCurrentProgress = array.getInt(R.styleable.PieProgressBar_progress, 0);
//是否渐变
isGradient = array.getBoolean(R.styleable.PieProgressBar_gradient, false);
//渐变颜色数组
CharSequence[] textArray = array.getTextArray(R.styleable.PieProgressBar_gradientColorArray);
if (textArray != null) {
colorArray = new int[textArray.length];
for (int i = 0; i < textArray.length; i++) {
colorArray[i] = Color.parseColor((String) textArray[i]);
}
}
mStartAngle = array.getInt(R.styleable.PieProgressBar_customAngle, 0);
array.recycle();
}
}

  这里声明了一些变量,然后写了3个构造方法,在第三个构造方法中进行属性的赋值。


三、测量


  这里测量就比较简单了,和之前的圆环进度条差不多,代码如下所示:

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = 0;
switch (MeasureSpec.getMode(widthMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST: //wrap_content
width = mRadius * 2;
break;
case MeasureSpec.EXACTLY: //match_parent
width = MeasureSpec.getSize(widthMeasureSpec);
break;
}
//Set the measured width and height
setMeasuredDimension(width, width);
}

  因为不需要进行子控件处理,所以我们只要一个圆和描边就行了,下面看绘制的方法。


四、绘制


  绘制这里就是绘制描边和进度,绘制的代码如下所示:

    @Override
protected void onDraw(Canvas canvas) {
int centerX = getWidth() / 2;
@SuppressLint("DrawAllocation")
RectF rectF = new RectF(0,0,centerX * 2,centerX * 2);
//绘制描边
drawStroke(canvas, centerX);
//绘制进度
drawProgress(canvas, rectF);
}

  在绘制之前首先要确定中心点,因为我们是一个圆环,实际上也是一个圆,圆的宽高一样,所以中心点的x、y轴的位置就是一样的,然后是确定一个矩形的左上和右下两个位置的坐标点,通过这两个点就能绘制一个矩形,接下来就是绘制进度条背景。


① 绘制描边

    /**
* 绘制描边
*
* @param canvas 画布
* @param centerX 中心点
*/
private void drawStroke(Canvas canvas, int centerX) {
Paint paint = new Paint();
paint.setColor(mProgressColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mStrokeWidth);
paint.setAntiAlias(true);
canvas.drawCircle(centerX, centerX, mRadius - (mStrokeWidth / 2), paint);
}

  这里的要点就是我们需要设置画笔的类型为描边,然后设置描边宽度,这样我们就可以画一个空心圆,就成了描边,然后我们绘制进度。


① 绘制进度

    /**
* 绘制进度条背景
*/
private void drawProgress(Canvas canvas, RectF rectF) {
Paint paint = new Paint();
//画笔的填充样式,Paint.Style.STROKE 描边
paint.setStyle(Paint.Style.FILL);
//抗锯齿
paint.setAntiAlias(true);
//画笔的颜色
paint.setColor(mProgressColor);
//是否设置渐变
if (isGradient && colorArray != null) {
paint.setShader(new RadialGradient(rectF.centerX(), rectF.centerY(), mRadius, colorArray, null, Shader.TileMode.MIRROR));
}
if (!isAnimation) {
mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress);
}
//开始画圆弧
canvas.drawArc(rectF, mStartAngle, mCurrentAngle, true, paint);
}

  因为背景是一个圆环,所以这里的画笔设置就比较注意一些,看一下就会了,这里最重要的是drawArc,用于绘制及角度圆,像下图这样,画了4/1的进度,同时增加是否渐变的设置,这里的开始角度是动态的。


在这里插入图片描述


五、API方法


  还需要提供一些方法在代码中调用,下面是这些方法的代码:

    /**
* 设置角度
* @param angle 角度
*/
public void setCustomAngle(int angle) {
if (angle >= 0 && angle < 90) {
mStartAngle = 0;
} else if (angle >= 90 && angle < 180) {
mStartAngle = 90;
} else if (angle >= 180 && angle < 270) {
mStartAngle = 180;
} else if (angle >= 270 && angle < 360) {
mStartAngle = 270;
} else if (angle >= 360) {
mStartAngle = 0;
}
invalidate();
}

/**
* 设置是否渐变
*/
public void setGradient(boolean gradient) {
isGradient = gradient;
invalidate();
}

/**
* 设置渐变的颜色
*/
public void setColorArray(int[] colorArr) {
if (colorArr == null) return;
colorArray = colorArr;
}

/**
* 设置当前进度
*/
public void setProgress(float progress) {
if (progress < 0) {
throw new IllegalArgumentException("Progress value can not be less than 0");
}
if (progress > mMaxProgress) {
progress = mMaxProgress;
}
mCurrentProgress = progress;
mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress);
setAnimator(mStartAngle, mCurrentAngle);
}

/**
* 设置动画
*
* @param start 开始位置
* @param target 结束位置
*/
private void setAnimator(float start, float target) {
isAnimation = true;
ValueAnimator animator = ValueAnimator.ofFloat(start, target);
animator.setDuration(mDuration);
animator.setTarget(mCurrentAngle);
//动画更新监听
animator.addUpdateListener(valueAnimator -> {
mCurrentAngle = (float) valueAnimator.getAnimatedValue();
invalidate();
});
animator.start();
}

  那么到此为止这个自定义View就完成了,下面我们可以在PieProgressBarActivity中使用了。


六、使用


   关于使用,我在写这个文章的时候这个自定义View已经加入到仓库中了,可以通过引入依赖的方式,例如在app模块中使用,则打开app模块下的build.gradle,在dependencies{}闭包下添加即可,之后记得要Sync Now

dependencies {
implementation 'io.github.lilongweidev:easyview:1.0.4'
}

   或者你在自己的项目中完成了刚才上述的所有步骤,那么你就不用引入依赖了,直接调用就好了,不过要注意更改对应的包名,否则会爆红的。


  先修改activity_pie_progress_bar.xml的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".used.PieProgressBarActivity">

<com.easy.view.PieProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:customAngle="right"
app:gradient="false"
app:gradientColorArray="@array/color"
app:maxProgress="100"
app:progress="5"
app:progressbarColor="@color/green"
app:radius="80dp" />

<CheckBox
android:id="@+id/cb_gradient"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="是否渐变" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始角度:"
android:textColor="@color/black" />

<RadioGroup
android:id="@+id/rg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<RadioButton
android:id="@+id/rb_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="0%" />

<RadioButton
android:id="@+id/rb_90"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="90%" />

<RadioButton
android:id="@+id/rb_180"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="180%" />

<RadioButton
android:id="@+id/rb_270"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="270%" />
</RadioGroup>
</LinearLayout>


<Button
android:id="@+id/btn_set_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="随机设置进度" />

<Button
android:id="@+id/btn_set_progress_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="设置0%进度" />

<Button
android:id="@+id/btn_set_progress_100"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="设置100%进度" />
</LinearLayout>

在strings.xml中增加渐变色,代码如下:

    <string-array name="color">
<item>#00FFF7</item>
<item>#FFDD00</item>
<item>#FF0000</item>
</string-array>

首先要注意看是否能够预览,我这里是可以预览的,如下图所示:


在这里插入图片描述


PieProgressBarActivity中使用,如下所示:

public class PieProgressBarActivity extends EasyActivity<ActivityPieProgressBarBinding> {

@SuppressLint("NonConstantResourceId")
@Override
protected void onCreate() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//是否渐变
binding.cbGradient.setOnCheckedChangeListener((buttonView, isChecked) -> {
binding.cbGradient.setText(isChecked ? "渐变" : "不渐变");
binding.progress.setGradient(isChecked);
});
//开始角度
binding.rg.setOnCheckedChangeListener((group, checkedId) -> {
int angle = 0;
switch (checkedId) {
case R.id.rb_0:
angle = 0;
break;
case R.id.rb_90:
angle = 90;
break;
case R.id.rb_180:
angle = 180;
break;
case R.id.rb_270:
angle = 270;
break;
}
binding.progress.setCustomAngle(angle);
});
//设置随机进度值
binding.btnSetProgress.setOnClickListener(v -> {
int progress = Math.abs(new Random().nextInt() % 100);
Toast.makeText(this, "" + progress, Toast.LENGTH_SHORT).show();
binding.progress.setProgress(progress);
});
//设置0%进度值
binding.btnSetProgress0.setOnClickListener(v -> binding.progress.setProgress(0));
//设置100%进度值
binding.btnSetProgress100.setOnClickListener(v -> binding.progress.setProgress(100));
}
}

运行效果如下图所示:


在这里插入图片描述


七、源码


如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~


源码地址:EasyView


作者:初学者_Study
链接:https://juejin.cn/post/7246453307736064060
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册