Demo完整代码地址
1. 分析:
该View主要有六个部分,底部背景
、左边进度条
、右边进度条
、横向半透明光柱
、比分文字
、光标
① 重写 onMeasure
,初始化一些值:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
width = halfDrawableWidth * 2;
}
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = halfDrawableHeight * 2;
}
// 记录进度条总长度
progressWidth = width;
// 记录进度条起始位置
barStartWidth = barPadding;
// 记录进度条结束位置
barEndWidth = progressWidth - barPadding;
setMeasuredDimension(width, height);
}
② 初始化画笔
// 底部边框背景
paintBackGround = new Paint();
paintBackGround.setColor(backGroundColor);
paintBackGround.setAntiAlias(true);
paintLight = new Paint();
paintLight.setAntiAlias(true);
// 左半部分进度条
paintBar = new Paint();
paintBar.setColor(barColor);
paintBar.setAntiAlias(true);
// 右半部分进度条
paintOtherBar = new Paint();
paintOtherBar.setColor(barColor);
paintOtherBar.setAntiAlias(true);
// 左半部分文字
paintLeftText = new Paint();
paintLeftText.setStyle(Paint.Style.FILL);
paintLeftText.setColor(textColor);
paintLeftText.setTextSize(textSize);
paintLeftText.setFakeBoldText(textIsBold); // 加粗
paintLeftText.setAntiAlias(true);
paintLeftText.setTextSkewX(-0.25f); // 斜体
// 右半部分文字
paintRightText = new Paint();
paintRightText.setStyle(Paint.Style.FILL);
paintRightText.setColor(textColor);
paintRightText.setTextSize(textSize);
paintRightText.setFakeBoldText(textIsBold);
paintRightText.setAntiAlias(true);
paintRightText.setTextSkewX(-0.25f);
③ 提前创建渐变进度条效果
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
//是否需要渐变器
if (isGradient) {
linearGradient = new LinearGradient(0, y / 2f,
progressWidth, y / 2f, gradientStartColor, gradientEndColor, Shader.TileMode.CLAMP);
linearGradientOther = new LinearGradient(0, y / 2f,
progressWidth, y / 2f, otherGradientStartColor, otherGradientEndColor, Shader.TileMode.CLAMP);
paintBar.setShader(linearGradient);
paintOtherBar.setShader(linearGradientOther);
}
return false;
}
});
开始绘制,重写onDraw()
方法。按照层叠关系依次绘制底部背景
、左边进度条
、右边进度条
、横向半透明光柱
、比分文字
、光标
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 光标水平位置
// 注: x一定不要用int类型
x = (float) ((progressWidth - 3 * barPadding) * progressPercentage + barPadding);
y = getHeight();
// 圆角大小,为实际进度条高度一半
if (isRound) {
barRadioSize = (y - barPadding * 2) / 2f;
}
// 绘制底部边框
drawBackground(canvas);
// 绘制左半部分进度条
drawLeftBar(canvas);
// 绘制右半部分进度条
drawRightBar(canvas);
// 绘制横向半透明光柱
drawLight(canvas);
// 绘制比例文字
drawRateText(canvas);
// 绘制光标
drawPicture(canvas);
}
④ 绘制底部背景
private void drawBackground(Canvas canvas) {
canvas.save();
rectFBG.set(0, 0, progressWidth, y);
if (isRound) {
// 底部背景圆角大小为高度的一半
canvas.drawRoundRect(rectFBG, y / 2f, y / 2f, paintBackGround);
} else {
canvas.drawRect(rectFBG, paintBackGround);
}
canvas.restore();
}
⑤ 绘制左半部分进度条
float right = getBoundaryPosition();
float[] radios = new float[]{
barRadioSize, barRadioSize, 0, 0,
0, 0, barRadioSize, barRadioSize};
if (progressPercentage == 1) {
radios[2] = barRadioSize;
radios[3] = barRadioSize;
radios[4] = barRadioSize;
radios[5] = barRadioSize;
}
canvas.save();
rectFPB.set(barPadding, barPadding, right, y - barPadding);
barRoundPath.reset();
barRoundPath.addRoundRect(rectFPB, radios, Path.Direction.CCW);
barRoundPath.close();
if (isRound) {
canvas.drawPath(barRoundPath, paintBar);
} else {
canvas.drawRect(rectFPB, paintBar);
}
canvas.restore();
⑥ 绘制右半部分进度条
float left = getBoundaryPosition();
float[] radios = new float[]{0, 0,
barRadioSize, barRadioSize,
barRadioSize, barRadioSize, 0, 0};
if (progressPercentage == 0) {
radios[0] = barRadioSize;
radios[1] = barRadioSize;
radios[6] = barRadioSize;
radios[7] = barRadioSize;
}
canvas.save();
rectFPBO.set(left, barPadding, barEndWidth, y - barPadding);
barRoundPathOther.reset();
barRoundPathOther.addRoundRect(rectFPBO, radios, Path.Direction.CCW);
barRoundPathOther.close();
if (isRound) {
canvas.drawPath(barRoundPathOther, paintOtherBar);
} else {
canvas.drawRect(rectFPBO, paintOtherBar);
}
canvas.restore();
⑦ 绘制横向半透明光柱
if (lightBitmap == null) {
return;
}
canvas.save();
// 将画布坐标系移动到画布中央
canvas.translate(0, barPadding);
Rect src = new Rect(barStartWidth, 0, barEndWidth, (y - barPadding) / 2);
canvas.drawBitmap(lightBitmap, src, src, paintLight);
canvas.restore();
⑧ 绘制比例文字
paintLeftText.getTextBounds(leftTextStr, 0, leftTextStr.length(), rectLeftText);
paintRightText.getTextBounds(rightTextStr, 0, rightTextStr.length(), rectRightText);
int des1W = rectRightText.width();
int desH = rectLeftText.height();
canvas.drawText(leftTextStr,
y / 2f,
y / 2f + desH / 3.0f,
paintLeftText);
canvas.drawText(rightTextStr,
progressWidth - y / 2f - des1W - barPadding,
y / 2f + desH / 3.0f,
paintRightText);
⑨ 绘制光标
if (drawable == null) {
return;
}
drawable.setBounds((int) getBoundaryPosition() - halfDrawableWidth, 0, (int) (getBoundaryPosition() + halfDrawableWidth), y);
drawable.draw(canvas);
⑩ 计算光标临界值
为保证美观,光标必须停留在直方区域,如下图所示
private float getBoundaryPosition() {
// 默认为计算的比例位置
float boundaryPos = x;
if (progressPercentage == 0 || x == barStartWidth) {
// 光标位于最左边
boundaryPos = barPadding;
} else if (progressPercentage == 1 || x == barEndWidth) {
// 光标位于最右边
boundaryPos = barEndWidth;
} else if (((x - barStartWidth) < barRadioSize || (x - barStartWidth) == barRadioSize)
&& x > barStartWidth) {
// 光标位于进度条左侧弧形区域
boundaryPos = Math.max(x, barRadioSize + barStartWidth);
} else if ((x > barEndWidth - barRadioSize || x == barEndWidth - barRadioSize)
&& x < barEndWidth) {
// 光标位于进度条右侧弧形区域
boundaryPos = Math.min(x, barEndWidth - barRadioSize);
}
return boundaryPos;
}