Android 自定义View 之 圆环进度条

圆环进度条

  • 前言
  • 正文
    • 一、XML样式
    • 二、构造方法
    • 三、测量
    • 四、绘制
      • ① 绘制进度条背景
      • ② 绘制进度
      • ③ 绘制文字
    • 五、API方法
    • 六、使用
    • 七、源码

前言

  很多时候我们会使用进度条,而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地址输入框来说的,代码如下所示:

    @Overrideprotected 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_contentwidth = mRadius * 2;break;case MeasureSpec.EXACTLY:   //match_parentwidth = MeasureSpec.getSize(widthMeasureSpec);break;}//Set the measured width and heightsetMeasuredDimension(width, width);}

  因为不需要进行子控件处理,所以我们只要一个圆环就可以了,文字在圆环中间绘制,下面再看绘制的方法。

四、绘制

  绘制这里稍微代码量多一些,因为需要绘制的内容有进度条背景、进度条、中间文字三个,绘制的代码如下所示:

    @Overrideprotected 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.MacAddressEditTextandroid: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" /><Buttonandroid:id="@+id/btn_mac"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="30dp"android:text="获取地址" /><com.easy.view.CircularProgressBarandroid: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" /><Buttonandroid: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

本文链接:https://my.lmcjl.com/post/4386.html

展开阅读全文

4 评论

留下您的评论.