package com.test.demo1.weight;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.test.demo1.R;
//https://cloud.tencent.com/developer/article/2271395
//https://hurui1990.github.io/hurui.github.io/2017/08/22/Android%E8%87%AA%E5%AE%9A%E4%B9%89View%E4%B9%8B%E8%87%AA%E5%B7%B1%E7%94%BB%E4%B8%80%E4%B8%AA%E6%97%B6%E9%92%9F/
//https://cloud.tencent.com/developer/article/1918397
//https://github.com/Blankj/ProgressRing
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 float mLastProgress;
/**
* 文字
*/
private String mText;
/**
* 文字颜色
*/
private int mTextColor;
/**
* 文字大小
*/
private float mTextSize;
/**
* 动画的执行时长
*/
private long mDuration = 1000;
/**
* 是否执行动画
*/
private boolean isAnimation = false;
/**
* 等级
*/
private Level level;
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();
}
@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;
if (level == Level.LV3) {
mTextColor = mProgressColor = Color.parseColor("#3EE0CE");
mProgressbarBgColor = Color.parseColor("#8CE7DF");
} else if (level == Level.LV2) {
mTextColor = mProgressColor = Color.parseColor("#E08540");
mProgressbarBgColor = Color.parseColor("#E7B48E");
} else {
mTextColor = mProgressColor = Color.parseColor("#E13F3F");
mProgressbarBgColor = Color.parseColor("#E68D8F");
}
// 绘制内侧红色圆环
drawInnerCircle(canvas, centerX);
//绘制进度条背景
drawProgressbarBg(canvas, rectF);
// //绘制进度
drawProgress(canvas, rectF);
//绘制中心文本
drawCenterText(canvas, centerX);
//绘制刻度
drawScale(canvas, centerX);
}
// 新增成员变量
private int mInnerCircleStrokeWidth = 10; // 内侧圆环宽度,默认10像素
private int mInnerCircleSpacing = 5; // 与进度条背景的间距,默认20像素
private int mInnerCircleColor = Color.RED; // 内侧圆环的颜色
/**
* 绘制内侧红色圆环
*/
private void drawInnerCircle(Canvas canvas, int centerX) {
Paint mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mInnerCircleStrokeWidth);
mPaint.setAntiAlias(true);
mPaint.setColor(mProgressbarBgColor);
mPaint.setStrokeCap(Paint.Cap.ROUND);
// 定义内侧圆环的绘制区域,考虑到间隔
float innerRadius = centerX - mInnerCircleSpacing - mInnerCircleStrokeWidth / 10;
// float innerRadius = 2;
RectF innerCircleRectF = new RectF(
centerX - innerRadius,
centerX - innerRadius,
centerX + innerRadius,
centerX + innerRadius);
// 绘制内侧红色圆环
canvas.drawArc(innerCircleRectF, 0, 360, false, mPaint);
}
private void drawScale(Canvas canvas, int centerX) {
Paint mPaintShort = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建了一个画笔(Paint),并进行一些属性的设置
Paint mPaintLong = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建了一个画笔(Paint),并进行一些属性的设置
mPaintLong.setColor(mProgressbarBgColor);
mPaintShort.setColor(mProgressColor);
// 计算内圆半径(刻度线开始位置)和外圆半径(刻度线结束位置)
float innerRadius = centerX - mStrokeWidth * 2; // 内圆半径,即进度条内侧边界
float outerRadiusForText = centerX - mTextSize - mStrokeWidth; // 文字与刻度线之间的间距
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) { // 显示整点数的较长刻度线
mPaintLong.setStrokeWidth(mStrokeWidth / 8); // 设置一个合适的宽度
// 长刻度线从内圆边缘开始绘制,向外延伸至接近文字区域
canvas.drawLine(
(float) (centerX + innerRadius * Math.cos(Math.toRadians(i * 6))), (float) (centerX + innerRadius * Math.sin(Math.toRadians(i * 6))),
(float) (centerX + outerRadiusForText * Math.cos(Math.toRadians(i * 6))), (float) (centerX + outerRadiusForText * Math.sin(Math.toRadians(i * 6))
), mPaintLong);
} else {
mPaintShort.setStrokeWidth(mStrokeWidth / 16); // 设置一个合适的宽度
// 短刻度线从内圆边缘开始绘制,向外延伸至接近文字区域
canvas.drawLine(
(float) (centerX + innerRadius * Math.cos(Math.toRadians(i * 6))), (float) (centerX + innerRadius * Math.sin(Math.toRadians(i * 6))),
(float) (centerX + (outerRadiusForText + mStrokeWidth / 4) * Math.cos(Math.toRadians(i * 6))), (float) (centerX + (outerRadiusForText + mStrokeWidth / 4) * Math.sin(Math.toRadians(i * 6))
), mPaintShort);
}
}
}
/**
* 绘制进度条背景
*/
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);
}
/**
* 绘制进度
*/
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);
}
mLastProgress = mCurrentAngle;
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);
}
/**
* 设置当前进度
*/
public void setProgress(float progress, Level level) {
this.level = level;
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);
if (mLastProgress == 0 || mLastProgress == mCurrentAngle) {
setAnimator(0, mCurrentAngle);
} else {
setAnimator(mLastProgress, 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();
}
public enum Level {
LV1, LV2, LV3
}
}
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_80"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="80" />
<Button
android:id="@+id/btn_60"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="60" />
<Button
android:id="@+id/btn_30"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="30" />
</LinearLayout>
<com.test.demo1.weight.CircularProgressBar
android:id="@+id/cpb_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
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>
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>
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.test.demo1.weight.CircularProgressBar;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
//圆形进度条操作
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, CircularProgressBar.Level.LV1);
});
findViewById(R.id.btn_80).setOnClickListener(v -> {
cpbTest.setText(80 + "");
cpbTest.setProgress(80, CircularProgressBar.Level.LV1);
});
findViewById(R.id.btn_60).setOnClickListener(v -> {
cpbTest.setText(60 + "");
cpbTest.setProgress(60, CircularProgressBar.Level.LV2);
});
findViewById(R.id.btn_30).setOnClickListener(v -> {
cpbTest.setText(30 + "");
cpbTest.setProgress(30, CircularProgressBar.Level.LV3);
});
}
}