前言
前面写了圆环进度条,这次我们来写一个饼状进度条,首先看一下效果图:
正文
效果图感觉怎么样呢?下面我们来实现这个自定义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