
package com.steelmate.company.views
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.annotation.Nullable
import com.steelmate.company.R
/**
* 分段进度条自定义View
* <p>
* 支持将进度条分为多个段(segment),每段之间留有间隔,
* 进度颜色从起始色到结束色渐变过渡,首尾段支持圆角绘制。
* 可通过XML属性或Java代码配置段数、颜色、圆角、间距、边框等样式。
* </p>
*/
public class SegmentedProgressBar extends View {
/** 背景层画笔:用于绘制进度条整体背景 */
private Paint backgroundPaint
/** 内容背景层画笔:用于绘制边框内部、进度条下方的白色内容区域背景 */
private Paint contentBackgroundPaint
/** 进度条画笔:用于绘制每一段已填充的进度色块 */
private Paint progressPaint
/** 边框画笔:用于绘制进度条外边框描边 */
private Paint borderPaint
/** 间隔线画笔:用于绘制各分段之间的间隔填充(白色间隔) */
private Paint spacingPaint
/** 最大分段数,默认5段 */
private int maxSegments = 5
/** 当前进度值,范围0~100 */
private int currentProgress = 0
/** 进度条整体圆角半径(像素) */
private float cornerRadius = 8f
/** 分段之间的间隔宽度(像素) */
private float segmentSpacing = 1f
/** 外边框描边宽度(像素) */
private float borderWidth = 2f
/** 边框与内容区域之间的内边距(像素) */
private float contentPadding = 0f
/** 进度条背景颜色,默认灰色
private int backgroundColor = Color.parseColor("#D0D0D0")
/** 内容区域背景颜色(边框内侧),默认白色 */
private int contentBackgroundColor = Color.WHITE
/** 进度条起始颜色(低进度端),默认绿色
private int progressStartColor = Color.parseColor("#4CAF50")
/** 进度条结束颜色(高进度端),默认浅绿色
private int progressEndColor = Color.parseColor("#8BC34A")
/** 外边框颜色,默认白色 */
private int borderColor = Color.WHITE
/** 进度条整体背景矩形区域 */
private RectF backgroundRect
/** 内容区域矩形(边框内侧) */
private RectF contentRect
/** 当前绘制段进度矩形区域(复用对象,避免频繁创建) */
private RectF progressRect
/** 进度段路径,用于实现选择性圆角绘制 */
private Path progressPath
public SegmentedProgressBar(Context context) {
this(context, null)
}
public SegmentedProgressBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0)
}
public SegmentedProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr)
init(context, attrs)
}
/**
* 初始化画笔、矩形区域及路径对象,并从XML属性集中读取自定义属性值
*
* @param context 上下文
* @param attrs XML属性集,可为null
*/
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentedProgressBar)
maxSegments = ta.getInt(R.styleable.SegmentedProgressBar_maxSegments, 5)
cornerRadius = ta.getDimension(R.styleable.SegmentedProgressBar_cornerRadius, 8f)
segmentSpacing = ta.getDimension(R.styleable.SegmentedProgressBar_segmentSpacing, 1f)
borderWidth = ta.getDimension(R.styleable.SegmentedProgressBar_borderWidth, 2f)
contentPadding = ta.getDimension(R.styleable.SegmentedProgressBar_contentPadding, 0f)
backgroundColor = ta.getColor(R.styleable.SegmentedProgressBar_backgroundColor, Color.parseColor("#D0D0D0"))
contentBackgroundColor = ta.getColor(R.styleable.SegmentedProgressBar_contentBackgroundColor, Color.WHITE)
progressStartColor = ta.getColor(R.styleable.SegmentedProgressBar_progressStartColor, Color.parseColor("#4CAF50"))
progressEndColor = ta.getColor(R.styleable.SegmentedProgressBar_progressEndColor, Color.parseColor("#8BC34A"))
borderColor = ta.getColor(R.styleable.SegmentedProgressBar_borderColor, Color.WHITE)
ta.recycle()
}
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG)
backgroundPaint.setColor(backgroundColor)
backgroundPaint.setStyle(Paint.Style.FILL)
contentBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG)
contentBackgroundPaint.setColor(contentBackgroundColor)
contentBackgroundPaint.setStyle(Paint.Style.FILL)
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG)
progressPaint.setStyle(Paint.Style.FILL)
borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG)
borderPaint.setColor(borderColor)
borderPaint.setStyle(Paint.Style.STROKE)
borderPaint.setStrokeWidth(borderWidth)
spacingPaint = new Paint(Paint.ANTI_ALIAS_FLAG)
spacingPaint.setColor(Color.WHITE)
spacingPaint.setStyle(Paint.Style.FILL)
backgroundRect = new RectF()
contentRect = new RectF()
progressRect = new RectF()
progressPath = new Path()
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec)
int height = MeasureSpec.getSize(heightMeasureSpec)
// 当高度模式为AT_MOST(如wrap_content)时,根据圆角、边框、内边距计算默认高度
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
height = (int) (cornerRadius * 2 + borderWidth * 2 + contentPadding * 2 + 10)
}
setMeasuredDimension(width, height)
}
/**
* 绘制进度条,绘制顺序:
* 1. 背景圆角矩形
* 2. 外边框描边
* 3. 内容区域背景(白色底)
* 4. 已填充的进度段(带选择性圆角)
* 5. 部分填充的进度段
* 6. 分段间隔线
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas)
float paddingLeft = getPaddingLeft()
float paddingRight = getPaddingRight()
float paddingTop = getPaddingTop()
float paddingBottom = getPaddingBottom()
// 1. 绘制背景圆角矩形
backgroundRect.set(paddingLeft, paddingTop,
getWidth() - paddingRight,
getHeight() - paddingBottom)
backgroundPaint.setColor(backgroundColor)
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint)
// 2. 绘制外边框
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, borderPaint)
// 3. 计算并绘制内容区域背景(边框内侧)
float innerLeft = paddingLeft + borderWidth + contentPadding
float innerTop = paddingTop + borderWidth + contentPadding
float innerRight = getWidth() - paddingRight - borderWidth - contentPadding
float innerBottom = getHeight() - paddingBottom - borderWidth - contentPadding
contentRect.set(innerLeft, innerTop, innerRight, innerBottom)
contentBackgroundPaint.setColor(contentBackgroundColor)
canvas.drawRoundRect(contentRect, cornerRadius / 2, cornerRadius / 2, contentBackgroundPaint)
if (currentProgress > 0) {
// 计算每段宽度和已填满的段数
float totalSpacing = segmentSpacing * (maxSegments - 1)
float segmentWidth = (contentRect.width() - totalSpacing) / maxSegments
int filledSegments = (int) ((float) currentProgress / 100f * maxSegments)
float segmentProgress = ((float) currentProgress / 100f * maxSegments) - filledSegments
// 4. 绘制已完全填充的进度段
for (int i = 0
float left = innerLeft + i * (segmentWidth + segmentSpacing)
float right = left + segmentWidth
progressRect.set(left, innerTop, right, innerBottom)
// 根据当前段位置计算颜色渐变比例
float progressRatio = (float) (i + 1) / maxSegments
int interpolatedColor = interpolateColor(progressStartColor, progressEndColor, progressRatio)
progressPaint.setColor(interpolatedColor)
// 首段左侧圆角,末段右侧圆角
boolean isFirstSegment = (i == 0)
boolean isLastSegment = (i == maxSegments - 1)
drawSegmentWithCorners(canvas, progressRect, cornerRadius / 2,
isFirstSegment, isLastSegment, isFirstSegment, isLastSegment)
}
// 5. 绘制部分填充的进度段(非整段进度)
if (filledSegments < maxSegments && segmentProgress > 0) {
float left = innerLeft + filledSegments * (segmentWidth + segmentSpacing)
float partialWidth = segmentWidth * segmentProgress
float right = left + partialWidth
if (right > left) {
progressRect.set(left, innerTop, right, innerBottom)
float progressRatio = (float) (filledSegments + segmentProgress) / maxSegments
int interpolatedColor = interpolateColor(progressStartColor, progressEndColor, progressRatio)
progressPaint.setColor(interpolatedColor)
boolean isFirstSegment = (filledSegments == 0)
boolean isLastSegment = (filledSegments == maxSegments - 1)
drawSegmentWithCorners(canvas, progressRect, cornerRadius / 2,
isFirstSegment, isLastSegment, isFirstSegment, isLastSegment)
}
}
// 6. 绘制分段间隔线
drawSpacingLines(canvas, innerLeft, innerTop, innerRight, innerBottom, segmentWidth)
}
}
/**
* 使用Path绘制带选择性圆角的进度段
* <p>
* 通过为矩形的四个角分别设置圆角半径(0或有值),实现首段左圆角、末段右圆角等效果。
* </p>
*
* @param canvas 画布
* @param rect 进度段矩形区域
* @param radius 圆角半径
* @param topLeft 是否绘制左上角圆角
* @param topRight 是否绘制右上角圆角
* @param bottomLeft 是否绘制左下角圆角
* @param bottomRight 是否绘制右下角圆角
*/
private void drawSegmentWithCorners(Canvas canvas, RectF rect, float radius,
boolean topLeft, boolean topRight,
boolean bottomLeft, boolean bottomRight) {
progressPath.reset()
// 构建8个值的圆角半径数组:每个角对应2个值(x半径, y半径)
float[] radii = new float[8]
radii[0] = topLeft ? radius : 0
radii[1] = topLeft ? radius : 0
radii[2] = topRight ? radius : 0
radii[3] = topRight ? radius : 0
radii[4] = bottomRight ? radius : 0
radii[5] = bottomRight ? radius : 0
radii[6] = bottomLeft ? radius : 0
radii[7] = bottomLeft ? radius : 0
progressPath.addRoundRect(rect, radii, Path.Direction.CW)
canvas.drawPath(progressPath, progressPaint)
}
/**
* 绘制分段之间的间隔线
* <p>
* 在每两个相邻段之间绘制白色矩形填充,视觉上形成分段间隔效果。
* </p>
*
* @param canvas 画布
* @param left 内容区域左边界
* @param top 内容区域上边界
* @param right 内容区域右边界
* @param bottom 内容区域下边界
* @param segmentWidth 单个段的宽度
*/
private void drawSpacingLines(Canvas canvas, float left, float top, float right, float bottom, float segmentWidth) {
for (int i = 1
float spacingX = left + i * (segmentWidth + segmentSpacing) - segmentSpacing
RectF spacingRect = new RectF(spacingX, top, spacingX + segmentSpacing, bottom)
canvas.drawRect(spacingRect, spacingPaint)
}
}
/**
* 在两个颜色之间进行线性插值
*
* @param color1 起始颜色
* @param color2 结束颜色
* @param fraction 插值比例,0.0~1.0
* @return 插值后的ARGB颜色值
*/
private int interpolateColor(int color1, int color2, float fraction) {
int a = (int) (Color.alpha(color1) + fraction * (Color.alpha(color2) - Color.alpha(color1)))
int r = (int) (Color.red(color1) + fraction * (Color.red(color2) - Color.red(color1)))
int g = (int) (Color.green(color1) + fraction * (Color.green(color2) - Color.green(color1)))
int b = (int) (Color.blue(color1) + fraction * (Color.blue(color2) - Color.blue(color1)))
return Color.argb(a, r, g, b)
}
/**
* 设置当前进度值
*
* @param progress 进度值,范围0~100,超出范围会自动裁剪
*/
public void setProgress(int progress) {
this.currentProgress = Math.min(100, Math.max(0, progress))
invalidate()
}
/**
* 获取当前进度值
*
* @return 当前进度,范围0~100
*/
public int getProgress() {
return currentProgress
}
/**
* 设置最大分段数
*
* @param segments 分段数量
*/
public void setMaxSegments(int segments) {
this.maxSegments = segments
invalidate()
}
/**
* 设置进度条渐变的起始和结束颜色
*
* @param startColor 起始颜色(低进度端)
* @param endColor 结束颜色(高进度端)
*/
public void setColors(int startColor, int endColor) {
this.progressStartColor = startColor
this.progressEndColor = endColor
invalidate()
}
}
```
```
```
res 下 color 文件新增如下颜色值
```
<!-- ... existing code ... -->
<color name="battery_progress_background">
<color name="battery_progress_start">
<color name="battery_progress_end">
<color name="battery_low_color">
<color name="battery_medium_color">
<color name="battery_high_color">
<!-- ... existing code ... -->
```
```
```
attr.xml 文件新增自定义样式属性
```
<!-- ... existing code ... -->
<declare-styleable name="SegmentedProgressBar">
<attr name="maxSegments" format="integer"/>
<attr name="cornerRadius" format="dimension"/>
<attr name="segmentSpacing" format="dimension"/>
<attr name="padding" format="dimension"/>
<attr name="contentPadding" format="dimension"/>
<attr name="borderWidth" format="dimension"/>
<attr name="backgroundColor" format="color"/>
<attr name="contentBackgroundColor" format="color"/>
<attr name="progressStartColor" format="color"/>
<attr name="progressEndColor" format="color"/>
<attr name="borderColor" format="color"/>
<attr name="dividerColor" format="color"/>
</declare-styleable>
<!-- ... existing code ... -->
```
```
```
layout 文件下新增自定义SegmentedProgressBar 文件
```
<com.steelmate.company.views.SegmentedProgressBar
android:id="@+id/progressBar"
android:layout_width="224dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:max="100"
android:progress="55"
app:maxSegments="5"
app:cornerRadius="8dp"
app:segmentSpacing="1dp"
app:borderWidth="4dp"
app:contentPadding="-2dp"
app:backgroundColor="#D0D0D0"
app:contentBackgroundColor="@color/transparent"
app:progressStartColor="#4CAF50"
app:progressEndColor="#8BC34A"
app:borderColor="#FFFFFF"
app:dividerColor="#FFFFFF"
app:layout_constraintStart_toStartOf="@id/ivBatteryIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvRemainingCapacity" />
```
```
private fun setupSegmentedProgress() {
val batteryLevel = 45
val estimatedRange = 42
contentView.progressBar.progress = batteryLevel
contentView.tvPercentage.text = "$batteryLevel%"
contentView.tvEstimatedRange.text = "预估里程 ${estimatedRange}km"
when {
batteryLevel <= 20 -> {
contentView.progressBar.setColors(Color.parseColor("#FF5722"), Color.parseColor("#FF5722"))
}
batteryLevel <= 50 -> {
contentView.progressBar.setColors(Color.parseColor("#FFC107"), Color.parseColor("#FFC107"))
}
else -> {
contentView.progressBar.setColors(Color.parseColor("#4CAF50"), Color.parseColor("#8BC34A"))
}
}
}
```
```