前言
很多时候我们会使用进度条,而Android默认的进度条是长条的,从左至右。而在日常开发中,有时候UI为了让页面更美观,就需要用到圆环进度条,那么本文就是通过自定义写一个圆环进度条,首先看一下效果图:
正文
关于自定义View的基础知识就不再做过多的讲解了,我们直接进入正题,这一次我们不需要再去创建项目了,就用我之前创建的EasyView。
一、XML样式
根据上面的效果图,我们首先来确定XML中的属性样式,修改attrs.xml的代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <resources> <!--文字颜色--> <attr name="textColor" format="color|reference" /> <!--文字大小--> <attr name="textSize" format="dimension" /> <!--蓝牙地址输入控件--> <declare-styleable name="MacAddressEditText"> <!-- 方框大小,宽高一致 --> <attr name="boxWidth" format="dimension" /> <!-- 方框背景颜色 --> <attr name="boxBackgroundColor" format="color|reference" /> <!-- 方框描边颜色 --> <attr name="boxStrokeColor" format="color|reference" /> <!-- 方框描边宽度 --> <attr name="boxStrokeWidth" format="dimension" /> <!--文字颜色--> <attr name="textColor" /> <!--文字大小--> <attr name="textSize" /> <!--分隔符,: 、- --> <attr name="separator" format="string|reference" /> </declare-styleable> <!--圆形进度条控件--> <declare-styleable name="CircularProgressBar"> <!--半径--> <attr name="radius" format="dimension" /> <!--进度条宽度--> <attr name="strokeWidth" format="dimension" /> <!--进度条背景颜色--> <attr name="progressbarBackgroundColor" format="color|reference" /> <!--进度条进度颜色--> <attr name="progressbarColor" format="color|reference" /> <!--最大进度--> <attr name="maxProgress" format="integer" /> <!--当前进度--> <attr name="progress" format="integer" /> <!--文字--> <attr name="text" format="string" /> <!--文字颜色--> <attr name="textColor" /> <!--文字大小--> <attr name="textSize" /> </declare-styleable> </resources>
这里你会发现一个改变,那就是文字颜色和文字大小的属性从之前的declare-styleable
中抽出来了,因为我们可能多个自定义控件会用到同样的属性,那么根据属性不可重名的原则,我们需要抽离出来,然后在declare-styleable
引用。
二、构造方法
现在属性样式已经有了,下一步就是写自定义View的构造方法了,在com.llw.easyview
包下新建一个CircularProgressBar
类,里面的代码如下所示:
public class CircularProgressBar extends View { /** * 半径 */ private int mRadius; /** * 进度条宽度 */ private int mStrokeWidth; /** * 进度条背景颜色 */ private int mProgressbarBgColor; /** * 进度条进度颜色 */ private int mProgressColor; /** * 开始角度 */ private int mStartAngle = 0; /** * 当前角度 */ private float mCurrentAngle = 0; /** * 结束角度 */ private int mEndAngle = 360; /** * 最大进度 */ private float mMaxProgress; /** * 当前进度 */ private float mCurrentProgress; /** * 文字 */ private String mText; /** * 文字颜色 */ private int mTextColor; /** * 文字大小 */ private float mTextSize; /** * 动画的执行时长 */ private long mDuration = 1000; /** * 是否执行动画 */ private boolean isAnimation = false; public CircularProgressBar(Context context) { this(context, null); } public CircularProgressBar(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircularProgressBar); mRadius = array.getDimensionPixelSize(R.styleable.CircularProgressBar_radius, 80); mStrokeWidth = array.getDimensionPixelSize(R.styleable.CircularProgressBar_strokeWidth, 8); mProgressbarBgColor = array.getColor(R.styleable.CircularProgressBar_progressbarBackgroundColor, ContextCompat.getColor(context, R.color.teal_700)); mProgressColor = array.getColor(R.styleable.CircularProgressBar_progressbarColor, ContextCompat.getColor(context, R.color.teal_200)); mMaxProgress = array.getInt(R.styleable.CircularProgressBar_maxProgress, 100); mCurrentProgress = array.getInt(R.styleable.CircularProgressBar_progress, 0); String text = array.getString(R.styleable.CircularProgressBar_text); mText = text == null ? "" : text; mTextColor = array.getColor(R.styleable.CircularProgressBar_textColor, ContextCompat.getColor(context, R.color.black)); mTextSize = array.getDimensionPixelSize(R.styleable.CircularProgressBar_textSize, (int) TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics())); array.recycle(); } }
这里声明了一些变量,然后写了3个构造方法,在第三个构造方法中进行属性的赋值。
三、测量
这里测量就比较简单了,当然这是相对于之前的那个Mac地址输入框来说的,代码如下所示:
@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; RectF rectF = new RectF(); rectF.left = mStrokeWidth; rectF.top = mStrokeWidth; rectF.right = centerX * 2 - mStrokeWidth; rectF.bottom = centerX * 2 - mStrokeWidth; //绘制进度条背景 drawProgressbarBg(canvas, rectF); //绘制进度 drawProgress(canvas, rectF); //绘制中心文本 drawCenterText(canvas, centerX); }
在绘制之前首先要确定中心点,因为我们是一个圆环,实际上也是一个圆,圆的宽高一样,所以中心点的x、y轴的位置就是一样的,然后是确定一个矩形的左上和右下两个位置的坐标点,通过这两个点就能绘制一个矩形,接下来就是绘制进度条背景。
① 绘制进度条背景
/** * 绘制进度条背景 */ private void drawProgressbarBg(Canvas canvas, RectF rectF) { Paint mPaint = new Paint(); //画笔的填充样式,Paint.Style.STROKE 描边 mPaint.setStyle(Paint.Style.STROKE); //圆弧的宽度 mPaint.setStrokeWidth(mStrokeWidth); //抗锯齿 mPaint.setAntiAlias(true); //画笔的颜色 mPaint.setColor(mProgressbarBgColor); //画笔的样式 Paint.Cap.Round 圆形 mPaint.setStrokeCap(Paint.Cap.ROUND); //开始画圆弧 canvas.drawArc(rectF, mStartAngle, mEndAngle, false, mPaint); }
因为背景是一个圆环,所以这里的画笔设置就比较注意一些,看一下就会了,这里最重要的是drawArc
,用于绘制圆弧,像下图这样,画了4/1的背景。
下面绘制进度
② 绘制进度
/** * 绘制进度 */ private void drawProgress(Canvas canvas, RectF rectF) { Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mStrokeWidth); paint.setColor(mProgressColor); paint.setAntiAlias(true); paint.setStrokeCap(Paint.Cap.ROUND); if (!isAnimation) { mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress); } canvas.drawArc(rectF, mStartAngle, mCurrentAngle, false, paint); }
这里的进度值就要根据当前的参数进行处理了,这里有一个变量进行判断处理,主要作用就是是否进行动画绘制。
③ 绘制文字
/** * 绘制中心文字 */ private void drawCenterText(Canvas canvas, int centerX) { Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(mTextColor); paint.setTextAlign(Paint.Align.CENTER); paint.setTextSize(mTextSize); Rect textBounds = new Rect(); paint.getTextBounds(mText, 0, mText.length(), textBounds); canvas.drawText(mText, centerX, textBounds.height() / 2 + getHeight() / 2, paint); }
绘制文字的规则还是和之前一样。
五、API方法
还需要提供一些方法在代码中调用,下面是这些方法的代码:
/** * 设置当前进度 */ 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(0, mCurrentAngle); } /** * 设置文本 */ public void setText(String text) { mText = text; } /** * 设置文本的颜色 */ public void setTextColor(int color) { if (color <= 0) { throw new IllegalArgumentException("Color value can not be less than 0"); } mTextColor = color; } /** * 设置文本的大小 */ public void setTextSize(float textSize) { if (textSize <= 0) { throw new IllegalArgumentException("textSize can not be less than 0"); } mTextSize = textSize; } /** * 设置动画 * * @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就完成了,下面我们可以在MainActivity中使用了。
六、使用
先修改activity_main.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" android:padding="16dp" tools:context=".MainActivity"> <com.easy.view.MacAddressEditText android:id="@+id/mac_et" android:layout_width="wrap_content" android:layout_height="wrap_content" app:boxBackgroundColor="@color/white" app:boxStrokeColor="@color/black" app:boxStrokeWidth="2dp" app:boxWidth="48dp" app:separator=":" app:textColor="@color/black" app:textSize="16sp" /> <Button android:id="@+id/btn_mac" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:text="获取地址" /> <com.easy.view.CircularProgressBar android:id="@+id/cpb_test" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" app:maxProgress="100" app:progress="10" app:progressbarBackgroundColor="@color/purple_500" app:progressbarColor="@color/purple_200" app:radius="80dp" app:strokeWidth="16dp" app:text="10%" app:textColor="@color/teal_200" app:textSize="28sp" /> <Button android:id="@+id/btn_set_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:text="随机设置进度" /> </LinearLayout>
首先要注意看是否能够预览,我这里是可以预览的,如下图所示:
在MainActivity中使用,修改onCreate()
方法中的代码,如下所示:
//圆形进度条操作 CircularProgressBar cpbTest = findViewById(R.id.cpb_test); Button btnSetProgress = findViewById(R.id.btn_set_progress); btnSetProgress.setOnClickListener(v -> { int progress = Math.abs(new Random().nextInt() % 100); Toast.makeText(this, "" + progress, Toast.LENGTH_SHORT).show(); cpbTest.setText(progress + "%"); cpbTest.setProgress(progress); });
运行效果如下图所示:
七、源码
顺便说一下,我打算把这个项目做成一个开源仓库,提交到mavenCentral()
中,后面使用的话就可以通过这个引入依赖的方式进行调用,会很方便,后面会单独出一篇文章讲述这个仓库。
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:EasyView