效果
attrs.xml
<attr name="textSpace" format="dimension|reference" />
<attr name="barSpace" format="dimension|reference" />
<attr name="scaleHeight" format="dimension|reference" />
<attr name="progressHeight" format="dimension|reference" />
<attr name="barRadius" format="dimension|reference" />
<attr name="barColor" format="color|reference" />
<attr name="barOutColor" format="color|reference" />
<attr name="textSize" format="dimension|reference" />
<attr name="strokeColor" format="color|reference" />
<attr name="strokeWidth" format="dimension|reference" />
<attr name="max" format="integer|reference" />
<attr name="progress" format="integer|reference" />
<declare-styleable name="AirQualityBar">
<attr name="textSpace" />
<attr name="barSpace" />
<attr name="scaleHeight" />
<attr name="progressHeight" />
<attr name="barRadius" />
<attr name="barColor" />
<attr name="barOutColor" />
<attr name="textSize" />
<attr name="strokeColor" />
<attr name="strokeWidth" />
<attr name="max" />
<attr name="progress" />
</declare-styleable>
使用
private String[] labels = new String[]{"优", "良", "轻度", "中度", "重度","严重"};
private int[] values = new int[]{35, 75, 115, 150,250};
private int[] colors = {
Color.parseColor("#0BCB81"),
Color.parseColor("#E0DE25"),
Color.parseColor("#F9A13A"),
Color.parseColor("#F93A3A"),
Color.parseColor("#B61455"),
Color.parseColor("#B61455"),
};
private float[] positions = {
0f, 0.2f, 0.4f, 0.6f, 0.8f,1.0f
};
AirQualityBar bar = holder.find(R.id.air_quality_bar);
bar.setGradient(colors , positions);
bar.setLabels(labels );
bar.setValues(values );
bar.setMax(360);
bar.setProgress(270, true);
源码
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import cn.anbao.forest.wards.R;
public class AirQualityBar extends View implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
private Paint paint;
private int textSpace = 10;
private int barSpace = 4;
private int scaleHeight = 4;
private int progressHeight = 12;
private int barRadius = 6;
private int barColor = Color.WHITE;
private int barOutColor = Color.parseColor("#80FFFFFF");
private int textSize = 14;
private int strokeColor = Color.parseColor("#687785");
private int strokeWidth = 2;
private int centerY;
private int width, height;
private int max = 100;
private int progress = 0;
private int paintProgress;
private String[] labels = new String[]{"优", "良", "轻度", "中度", "重度","严重"};
private int[] values = new int[]{35, 75, 115, 150,250};
private int[] colors = {
Color.parseColor("#0BCB81"),
Color.parseColor("#E0DE25"),
Color.parseColor("#F9A13A"),
Color.parseColor("#F93A3A"),
Color.parseColor("#B61455"),
Color.parseColor("#B61455"),
};
private int[] paintColors;
private float[] positions = {
0f, 0.2f, 0.4f, 0.6f, 0.8f,1.0f
};
private float[] paintPositions;
private int progressRadius;
private ValueAnimator animator;
public AirQualityBar(Context context) {
this(context, null);
}
public AirQualityBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AirQualityBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public AirQualityBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initAttributeSet(context, attrs, defStyleAttr);
}
private void initAttributeSet(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
initAnimator();
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AirQualityBar, defStyleAttr, 0);
textSpace = array.getDimensionPixelOffset(R.styleable.AirQualityBar_textSpace, textSpace);
barSpace = array.getDimensionPixelOffset(R.styleable.AirQualityBar_barSpace, barSpace);
scaleHeight = array.getDimensionPixelOffset(R.styleable.AirQualityBar_scaleHeight, scaleHeight);
progressHeight = array.getDimensionPixelOffset(R.styleable.AirQualityBar_progressHeight, progressHeight);
barRadius = array.getDimensionPixelOffset(R.styleable.AirQualityBar_barRadius, barRadius);
barColor = array.getColor(R.styleable.AirQualityBar_barColor, barColor);
barOutColor = array.getColor(R.styleable.AirQualityBar_barOutColor, barOutColor);
textSize = array.getDimensionPixelSize(R.styleable.AirQualityBar_textSize, textSize);
strokeColor = array.getColor(R.styleable.AirQualityBar_strokeColor, strokeColor);
strokeWidth = array.getDimensionPixelOffset(R.styleable.AirQualityBar_strokeWidth, strokeWidth);
max = array.getInt(R.styleable.AirQualityBar_max, max);
progress = array.getInt(R.styleable.AirQualityBar_progress, progress);
array.recycle();
}
setProgress(progress);
paintColors = colors;
paintPositions = positions;
paintProgress = progress;
}
private void initAnimator() {
if (animator == null) {
animator = new ValueAnimator();
animator.setDuration(500);
animator.addUpdateListener(this);
animator.addListener(this);
}
}
@Override
public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
paintProgress = (int) valueAnimator.getAnimatedValue();
findSuitableColorsPositions(paintProgress);
invalidate();
}
@Override
public void onAnimationStart(@NonNull Animator animator) {
}
@Override
public void onAnimationEnd(@NonNull Animator animator) {
release();
}
@Override
public void onAnimationCancel(@NonNull Animator animator) {
}
@Override
public void onAnimationRepeat(@NonNull Animator animator) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
centerY = height / 2;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
paint.setTextSize(textSize);
paint.setColor(strokeColor);
progressRadius = progressHeight / 2;
RectF bounds = new RectF(0, centerY - progressRadius, width, centerY + progressRadius);
canvas.drawRoundRect(bounds, progressRadius, progressRadius, paint);
float percent = getRealProgressValue(paintProgress) * 1.0F / max;
paint.setStyle(Paint.Style.FILL);
RectF rectF = new RectF(barSpace, centerY - progressRadius + barSpace, barSpace + (width - 2 * barSpace) * percent, centerY + progressRadius - barSpace);
if (paintColors.length > 1 && paintPositions.length > 1) {
paint.setShader(new LinearGradient(rectF.left, rectF.top, rectF.right, rectF.top, paintColors, paintPositions, Shader.TileMode.CLAMP));
} else {
paint.setColor(paintColors[0]);
}
canvas.drawRoundRect(rectF, progressRadius, progressRadius, paint);
if (percent > 0) {
paint.setShader(null);
int horizontal = width - 2 * barSpace;
boolean isEnd = percent >= 0.95f;
float cx = isEnd ? horizontal * percent - barSpace : horizontal * percent - barSpace/2;
float cy = centerY;
paint.setColor(barOutColor);
canvas.drawCircle(cx + barSpace, cy, barRadius, paint);
paint.setColor(barColor);
float innerRadius = barRadius * 0.65f;
canvas.drawCircle(cx + innerRadius, cy, innerRadius, paint);
}
drawScale(canvas, labels, values);
}
private int getRealProgressValue(int progress) {
return calculateProgress(values, max, progress);
}
private int calculateProgress(int[] values, int max, int value) {
int[] mixValues = Arrays.copyOf(values, values.length + 1);
mixValues[mixValues.length - 1] = max;
int segmentValue = max / (mixValues.length);
int progress = 0;
for (int i = 0; i < mixValues.length; i++) {
int preIndex = i - 1;
int preValue = preIndex > -1 ? mixValues[preIndex] : 0;
int itemValue = mixValues[i];
if (value > preValue && value <= itemValue) {
int diffValue = itemValue - preValue;
int segmentProgress = (value - preValue) * segmentValue / diffValue;
progress = (preIndex < 0 ? 0 : segmentValue * (preIndex + 1)) + segmentProgress;
}
}
return progress;
}
public float getPercent() {
return getRealProgressValue(progress) * 1.0F / max;
}
private List<Integer> colorList;
private List<Float> positionList;
private void findSuitableColorsPositions(int progress) {
float percent = progress * 1.0F / max;
if (positions == null || colors == null) {
return;
}
if (positions.length == 0 || colors.length == 0) {
return;
}
if (colorList == null) {
colorList = new ArrayList<>();
} else {
colorList.clear();
}
if (positionList == null) {
positionList = new ArrayList<>();
} else {
positionList.clear();
}
for (int i = 0; i < positions.length; i++) {
if (percent > positions[i]) {
positionList.add(positions[i]);
colorList.add(colors[i]);
}
}
int colorSize = colorList.size();
if (colorSize == 0) {
return;
}
paintColors = new int[colorSize];
for (int i = 0; i < colorList.size(); i++) {
paintColors[i] = colorList.get(i);
}
int positionSize = positionList.size();
if (positionSize == 0) {
return;
}
paintPositions = new float[positionSize];
float itemValue = 1.0F / positionSize;
for (int i = 0; i < positionList.size(); i++) {
paintPositions[i] = itemValue * (i + 1);
}
}
private void drawScale(Canvas canvas, String[] labels, int[] values) {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(strokeColor);
paint.setStrokeWidth(strokeWidth);
paint.setTextSize(textSize);
int labelSize = labels.length;
int labelItemWidth = width / labelSize;
for (int i = 0; i < labelSize; i++) {
float startX = labelItemWidth * (i + 1);
float startY = centerY - progressRadius;
float stopX = labelItemWidth * (i + 1);
float stopY = startY - scaleHeight;
if (i < labelSize - 1) {
canvas.drawLine(startX, startY, stopX, stopY, paint);
}
String label = labels[i];
float x = startX - labelItemWidth / 2 - measureText(paint, label).width() / 2;
float y = startY - textSpace;
canvas.drawText(labels[i], x, y, paint);
}
int valueCount = values.length;
int valueItemWidth = width / labelSize;
for (int i = 0; i < valueCount; i++) {
float startX = valueItemWidth * (i + 1);
float startY = centerY + progressRadius;
float stopX = valueItemWidth * (i + 1);
float stopY = startY + scaleHeight;
canvas.drawLine(startX, startY, stopX, stopY, paint);
String value = values[i] + "";
Rect bounds = measureText(paint, value);
float x = startX - bounds.width() / 2;
float y = stopY + textSpace + bounds.height() / 2;
canvas.drawText(value, x, y, paint);
}
}
private Rect measureText(Paint paint, String text) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
return bounds;
}
public void setMax(int max) {
this.max = max;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
setProgress(progress, true);
}
public void setProgress(int progress, boolean animator) {
if (getProgress() == progress) {
return;
}
progress = progress < 0 ? 0 : progress;
if (progress == 0) {
paintProgress = 0;
findSuitableColorsPositions(paintProgress);
invalidate();
return;
}
this.progress = progress;
if (animator) {
startAnimator(progress);
} else {
paintProgress = progress;
findSuitableColorsPositions(paintProgress);
invalidate();
}
}
private void startAnimator(int value) {
initAnimator();
animator.setIntValues(0, value);
animator.start();
}
public void setGradient(int[] colors, float[] positions) {
this.colors = colors;
this.positions = positions;
invalidate();
}
public void setLabels(String[] labels) {
this.labels = labels;
invalidate();
}
public void setValues(int[] values) {
this.values = values;
invalidate();
}
public String getLabel() {
String label = "";
float value = getPercent();
int length = labels.length;
for (int i = 0; i < length; i++) {
float min = i * 1.F / length;
float max = (i + 1) * 1.0F / length;
if (value >= min && value < max) {
label = labels[i];
}
}
return label;
}
public void release() {
if (animator != null) {
animator.cancel();
animator.removeAllListeners();
animator.removeAllUpdateListeners();
animator = null;
}
}
}