原因
UI需要一个有跑道轨迹效果的进度条,但是搜索看到的,普遍都是圆形或者水平的进度条,根本不符合要求,只能直接自定义一个
新增属性
在attrs.xml下新增属性
<declare-styleable name="TrackProgressView">
<!-- 内圆水平宽度-->
<attr name="innerWidth" format="dimension" />
<!-- 圆半径-->
<attr name="radius" format="dimension" />
<!-- 背景色-->
<attr name="bgColor" format="reference" />
<!-- 轨迹宽度-->
<attr name="trackWidth" format="dimension" />
<!-- 轨迹颜色-->
<attr name="trackColor" format="reference" />
<!-- 最大进度-->
<attr name="maxProgress" format="integer" />
<!-- 当前进度-->
<attr name="currentProgress" format="integer" />
<!-- 进度条颜色-->
<attr name="progressColor" format="integer" />
<!-- 字体颜色-->
<attr name="textColor" format="reference" />
<!-- 字体大小-->
<attr name="textSize" format="dimension" />
<!-- 提示文字-->
<attr name="tipText" format="string" />
<!-- 是否显示文字-->
<attr name="showText" format="boolean" />
<!-- 是否显示提示-->
<attr name="showTips" format="boolean" />
自定义View
分析
1、一个圆分垂直分割成2个半圆,半圆之间一条直线
2、计算圆上坐标点:根据角度 (i),半径(mRadius),获取坐标点
公式:x 坐标 = mRadius*Math.cos(Math.toRadians(i)
y 坐标 = mRadius * Math.sin(Math.toRadians(i)
3、角度从-180 到 180,顺时针计算每一个角度坐标
4、当角度 等于-90度,添加一条从左往右的直线坐标点
5、当角度 等于90度,添加一条从右往左的坐标点
6、把所有坐标点连在一起,组成一个圆角矩形
7、轨道坐标点,就是在圆角矩形加上宽度(水平方向 y + 或 - 宽度,垂直方向x +或 -宽度)
实现步骤
1、需要继承View
2、计算内圆角矩形坐标点
3、根据圆角矩形坐标点获取轨道坐标点
最终实现源码
/**
* @package com.poso2o.lottery_shop.ui.custom
* @filename TrackProgressView
* @author:sen
* @date 2021/3/2 5:15 PM
* @function: 轨迹进度条
*/
public class TrackProgressView extends View {
private int mWidth = 100;
private int mRadius = 50;
private int mTrackWidth = 20;
/**
* 背景色
*/
private int mBgColor = 0;
/**
* 轨迹动画颜色
*/
private int mTrackColor = 0;
/**
* 进度颜色
*/
private int mProgressColor = 0;
private int mTextColor = 0;
/**
* 最大进度
*/
private int mMaxProgress = 100;
/**
* 当前进度
*/
private int mCurrentProgress = 10;
private int mTextSize = 0;
private String mTipsText = "未来3天预约率";
private boolean mShowText = true;
private boolean mShowTips = true;
private float rote = 0f;
private int lastIndex = 0;
private Paint mBgPaint = new Paint();
private Paint mTrackPaint = new Paint();
private Paint mProgressPaint = new Paint();
private Paint mTextPaint = new Paint();
/**
* 圆角矩形
*/
private List<Point> mPathPointList = new ArrayList<Point>();
private Path mBgPath = new Path();
/**
* 轨迹
*/
private List<Point> mTrackPointList = new ArrayList<Point>();
private Path mTrackPath = new Path();
/**
* 进度
*/
private Path mProgressPath = new Path();
public TrackProgressView(Context context) {
this(context, null);
}
public TrackProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TrackProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//默认参数
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TrackProgressView, defStyleAttr, 0);
//内圆矩形背景色
mBgColor = typedArray.getColor(R.styleable.TrackProgressView_bgColor, getResources().getColor(R.color.red));
//内圆半径
mRadius = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_radius, 50);
//内圆水平宽度
mWidth = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_innerWidth, 100);
// mWidth = 2 * mRadius;
LogUtils.INSTANCE.d("mWidth>>>>" + mWidth);
//轨迹宽度
mTrackWidth = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_trackWidth, 20);
//轨迹颜色
mTrackColor = typedArray.getColor(R.styleable.TrackProgressView_trackColor, getResources().getColor(R.color.orange));
//进度条颜色
mProgressColor = typedArray.getColor(R.styleable.TrackProgressView_progressColor, getResources().getColor(R.color.white));
//最大进度
mMaxProgress = typedArray.getInteger(R.styleable.TrackProgressView_maxProgress, 100);
//当前进度
mCurrentProgress = typedArray.getInteger(R.styleable.TrackProgressView_currentProgress, 10);
//字体颜色
mTextSize = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_textSize, 20);
mTextColor = typedArray.getColor(R.styleable.TrackProgressView_textColor, getResources().getColor(R.color.white));
mShowText = typedArray.getBoolean(R.styleable.TrackProgressView_showText, true);
mShowTips = typedArray.getBoolean(R.styleable.TrackProgressView_showTips, true);
mTipsText = typedArray.getString(R.styleable.TrackProgressView_tipText);
if (TextUtils.isEmpty(mTipsText)) {
mTipsText = "未来3天预约率";
}
typedArray.recycle();
initPaint();
//内部圆角矩形
initList();
float startX = 0;
float startY = 0;
//圆角矩形路径
for (int i = 0; i < mPathPointList.size(); i++) {
Point point = mPathPointList.get(i);
if (i == 0) {
startX = point.getX();
startY = point.getY();
mBgPath.moveTo(startX, startY);
}
mBgPath.quadTo(startX, startY, point.getX(), point.getY());
startX = point.getX();
startY = point.getY();
}
//外部边框
for (int i = 0; i < mTrackPointList.size(); i++) {
Point point = mTrackPointList.get(i);
if (i == 0) {
startX = point.getX();
startY = point.getY();
mTrackPath.moveTo(startX, startY);
}
mTrackPath.quadTo(startX, startY, point.getX(), point.getY());
startX = point.getX();
startY = point.getY();
}
LogUtils.INSTANCE.d("size =" + (mTrackPointList.size()) + ",mCurrentProgress = " + mCurrentProgress);
setMaxProgress(mMaxProgress);
setProgress(mCurrentProgress, false);
}
private void initList() {
for (int i = -180; i <= 180; i++) {
int outRadius = mRadius + mTrackWidth;
float pointX = (float) (outRadius + mRadius * Math.cos(Math.toRadians(i)));
float pointY = (float) (outRadius + mRadius * Math.sin(Math.toRadians(i)));
//外边框 半径 = 内 + 宽度/2
float outX = (float) (outRadius + (mRadius + mTrackWidth / 2) * Math.cos(Math.toRadians(i)));
float outY = (float) (outRadius + (mRadius + mTrackWidth / 2) * Math.sin(Math.toRadians(i)));
//左半圆 左上扇形 -180~-90 ,左下扇形 90~180
//右半圆 右上扇形 -90~0 右下扇形 0~90
// LogUtils.INSTANCE.d("角度 = " + i + ",pointX = " + pointX + ", pointY = " + pointY);
if (i >= -180 && i <= -90) {
if (i == -90) {
//添加一条横线 270个点
int count = 0;
//占比
for (int j = 0; j <= mWidth; j++) {
count = j;
mPathPointList.add(new Point(pointX + count, pointY));
//上边框
mTrackPointList.add(new Point(outX + count, outY));
}
// LogUtils.INSTANCE.d("count = "+ count);
continue;
} else {
//左上扇形
mPathPointList.add(new Point(pointX, pointY));
//
mTrackPointList.add(new Point(outX, outY));
}
}
if (i >= -90 && i <= 0) {
//右上扇形
mPathPointList.add(new Point(pointX + mWidth, pointY));
//右边框
mTrackPointList.add(new Point(outX + mWidth, outY));
}
if (i >= 0 && i <= 90) {
//右下扇形
if (i == 90) {
//添加一条横线
int count = 0;
for (int j = mWidth; j >= 0; j--) {
count = j;
mPathPointList.add(new Point(pointX + count, pointY));
//
mTrackPointList.add(new Point(outX + count, outY));
}
continue;
} else {
mPathPointList.add(new Point(pointX + mWidth, pointY));
//边框
mTrackPointList.add(new Point(outX + mWidth, outY));
}
}
if (i >= 90 && i <= 180) {
//左下扇形
mPathPointList.add(new Point(pointX, pointY));
//
mTrackPointList.add(new Point(outX, outY));
}
}
}
private void initPaint() {
//圆角矩形
mBgPaint.setColor(mBgColor);
mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
//轨迹
mTrackPaint.setColor(mTrackColor);
mTrackPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTrackPaint.setStyle(Paint.Style.STROKE);
mTrackPaint.setStrokeWidth(mTrackWidth);
//进度
mProgressPaint.setColor(mProgressColor);
mProgressPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(mTrackWidth);
//文字
mTextPaint.setColor(mTextColor);
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
// mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setTextSize(mTextSize);
}
public void setMaxProgress(int maxProgress) {
this.mMaxProgress = maxProgress;
//计算百分比
rote = mTrackPointList.size() * 1f / mMaxProgress * 1f;
LogUtils.INSTANCE.d("size =" + (mTrackPointList.size()) + ",mMaxProgress = " + maxProgress + " , rote = " + rote);
lastIndex = 0;
}
public void resetProgress() {
lastIndex = 0;
this.mCurrentProgress = 0;
//重新绘制
mProgressPath = new Path();
Point point = mTrackPointList.get(0);
mProgressPath.moveTo(point.getX(), point.getY());
invalidate();
}
private void setProgress(int progress, boolean refresh) {
if (progress > mMaxProgress) {
this.mCurrentProgress = mMaxProgress;
} else {
this.mCurrentProgress = progress;
}
if (progress == 0) {
//重置
resetProgress();
return;
}
mProgressPaint.setColor(mProgressColor);
LogUtils.INSTANCE.d("size =" + (mTrackPointList.size()) + ",mCurrentProgress = " + mCurrentProgress);
//根据百分比,获取当前索引
int maxIndex = (int) (mCurrentProgress * rote);
LogUtils.INSTANCE.d("lastIndex = " + lastIndex);
LogUtils.INSTANCE.d("maxIndex = " + maxIndex);
if (maxIndex >= mTrackPointList.size()) {
maxIndex = mTrackPointList.size() - 1;
}
float startX = 0;
float startY = 0;
for (int i = lastIndex; i <= maxIndex; i++) {
Point point = mTrackPointList.get(i);
if (i == lastIndex) {
startX = point.getX();
startY = point.getY();
mProgressPath.moveTo(startX, startY);
}
mProgressPath.quadTo(startX, startY, point.getX(), point.getY());
startX = point.getX();
startY = point.getY();
}
lastIndex = maxIndex;
if (refresh) {
//重新绘制
invalidate();
}
}
public void setProgress(int progress) {
setProgress(progress, true);
}
public void setTipsText(String tipsText) {
this.mTipsText = tipsText;
invalidate();
}
public void setShowText(boolean showText){
this.mShowText = showText;
invalidate();
}
public void setShowTips(boolean showTips){
this.mShowTips = showTips;
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
LogUtils.INSTANCE.d("onMeasure::widthMode=" + widthMode + "; widthSize="
+ widthSize);
LogUtils.INSTANCE.d("onMeasure::heightMode=" + heightMode + "; heightSize="
+ heightSize);
int outRadius = mRadius + mTrackWidth;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
//外边框直径 + 内圆矩形宽度
width = (int) (getPaddingLeft() + (mWidth + outRadius * 2) + getPaddingRight());
textStartX = width / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
//外边框直径
height = (int) (getPaddingTop() + (outRadius * 2) + getPaddingBottom());
textStartY = height / 2;
}
setMeasuredDimension(width, height);
LogUtils.INSTANCE.d("onMeasure>>>>");
}
private int textStartX = 0;
private int textStartY = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//内圆角矩形
canvas.drawPath(mBgPath, mBgPaint);
//轨迹边框
canvas.drawPath(mTrackPath, mTrackPaint);
mProgressPaint.setColor(mProgressColor);
//进度
canvas.drawPath(mProgressPath, mProgressPaint);
//进度百分比文字 ,居中,文字宽度,高度
if (mShowText) {
Rect textRect = new Rect();
String textName = String.format("%.2f", (mCurrentProgress * 1f / mMaxProgress) * 100.0) + "%";
mTextPaint.setTextSize(mTextSize);
mTextPaint.getTextBounds(textName, 0, textName.length(), textRect);
if (mShowTips) {
canvas.drawText(textName, textStartX - textRect.width() / 2, textStartY, mTextPaint);
} else {
//居中
canvas.drawText(textName, textStartX - textRect.width() / 2, textStartY + textRect.height() / 2, mTextPaint);
}
}
if (mShowTips) {
//进度提示文字
mTextPaint.setTextSize(mTextSize / 2);
Rect textRect2 = new Rect();
mTextPaint.getTextBounds(mTipsText, 0, mTipsText.length(), textRect2);
if (mShowText) {
canvas.drawText(mTipsText, textStartX - textRect2.width() / 2, textStartY + (int) (1.5 * textRect2.height()), mTextPaint);
} else {
//居中
canvas.drawText(mTipsText, textStartX - textRect2.width() / 2, textStartY + textRect2.height() / 2, mTextPaint);
}
}
}
static class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
}
使用
1、布局中添加TrackProgressView
<com.poso2o.lottery_shop.ui.custom.TrackProgressView
android:id="@+id/trackProgressView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:bgColor="@color/main_color"
app:currentProgress="1"
app:innerWidth="50dp"
app:maxProgress="100"
app:progressColor="@color/white"
app:radius="20dp"
app:showTips="true"
app:showText="true"
app:textColor="@color/white"
app:textSize="20dp"
app:tipText="未来5天预约率"
app:trackColor="@color/red"
app:trackWidth="10dp" />
2、代码中操作进度
trackProgressView.setProgress(progressCount)
btnAddProgress.setOnClickListener {
progressCount += 10
trackProgressView.setProgress(progressCount)
}
3、效果