先上图
(背景色变化很丑,请忽略哈。主要是演示一下效果哈哈哈!)
分析
首先看到这个效果。分析一下有哪些组成部分
- 背景
- 外层的实线圆弧
- 里层的一圈点
- 中间两行字
代码
话不多说开撸,先定个小目标,继承个View类再说
public class YbbView extends View {
private Paint mPaint; //画笔
private int width; //view的宽
private int height;//view的高
public YbbView(Context context) {
super(context);
}
public YbbView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
复制代码
重写onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getMeasureSize(100, widthMeasureSpec);
int height = getMeasureSize(100, heightMeasureSpec);
setMeasuredDimension(width, (int) (height * 0.8f));
}
private int getMeasureSize(int def, int measureSpec) {
int measuresize = 0;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.UNSPECIFIED) {
measuresize = def;
} else if (mode == MeasureSpec.EXACTLY) {
measuresize = size;
} else if (mode == MeasureSpec.AT_MOST) {
measuresize = Math.min(def, size);
}
return measuresize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = (int) (h / 0.8f);
}
复制代码
emmm,到这可能有人要问了,(没人问我也要说。。) 为啥 (height * 0.8f)
高要乘以0.8呢?其实最开始是没有这个0.8的,不过后来我发现了一个问题,就画个图来表示一下吧~~ 看下图
一个正圆的高度能填满整个View,但是我们只要一部分弧度,所以下面那一部分红色的...姑且叫阴影部分吧,阴影部分就空余出来了,虽然看着只有上面部分,但是View实际的高却要加上阴影部分,很占地方。我开始想过把画布往下移动,但是还是有多余的部分。。最后就想了这么一个办法,可能会损失一些精度。如果有更好的办法请大牛留言指教一下 =_= 。
绘制背景
/**
* 绘制view的背景
*/
private void drawBackground(Canvas canvas) {
canvas.drawColor(backColor);
}
复制代码
绘制外层圆弧
private void drawArc(Canvas canvas) {
int d = Math.min(width, height);
float f = TypeValueUtil.dp2px(metrics, 7); /这个弧线距离view四周的margin
float r = d / 2 - f;
float left = d / 2 - r;
float right = d / 2 + r;
arcRectF = new RectF(left, f, right, d - f);
mPaint.setStrokeWidth(arcWidth);
mPaint.setAntiAlias(true);//设置无锯齿
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setShader(getGradient(d, arcColors, startArc));
canvas.drawArc(arcRectF, startArc, drawEndArc, false, mPaint);
}
/**
* 得到一个渐变的圆弧叠加层
*
* @param d 圆的直径
* @param colors 渐变数组
* @param startArc 从什么角度开始渐变
* @return
*/
private SweepGradient getGradient(int d, int[] colors, int startArc) {
SweepGradient sweepGradient = new SweepGradient(d / 2.0f, d / 2.0f, colors, null);
//旋转 不然是从0度开始渐变
Matrix matrix = new Matrix();
matrix.setRotate(startArc, d / 2.0f, d / 2.0f);
sweepGradient.setLocalMatrix(matrix);
return sweepGradient;
}
复制代码
这里有一个渐变颜色的图层,SweepGradient
它默认是从0度开始渐变的,但是我们想要的却不是,所以要进行一个旋转。让他的0度和我们想要的角度重合。
绘制里层一圈刻度
private void drawPoint(Canvas canvas) {
float d = Math.min(width, height);
float f = (int) TypeValueUtil.dp2px(metrics, 18);
float r = d / 2 - f;
float left = d / 2 - r;
float right = d / 2 + r;
RectF rectF = new RectF(left, f, right, d - f);
mPaint.setStrokeWidth(pointWidth);
int startArc = this.startArc;
for (int i = 0; i <= pointCount; i++) {
canvas.drawArc(rectF, startArc, pointLength, false, mPaint);
startArc = startArc + nullLength + pointLength;
}
}
复制代码
这里为了可以让刻度可以随意变宽变长所以也用弧度来做。
绘制两行文字
private void drawText(Canvas canvas) {
int top = (int) (height / 3 + TypeValueUtil.dp2px(metrics, 15));
int left = width / 2;
mPaint.setTextSize(tipTextSize);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextAlign(Paint.Align.CENTER);
String text = "涂山苏苏多少岁?";
float textW = mPaint.measureText(text);
LinearGradient lg = new LinearGradient(left, top, left + textW, top, arcColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(lg);
canvas.drawText(text, left, top, mPaint);
}
private void drawNumText(Canvas canvas) {
int top = (int) (height / 2 + TypeValueUtil.dp2px(metrics, 15));
int left = width / 2;
mPaint.setTextSize(amtTextSize);
String text = format.format(amt);
float textW = mPaint.measureText(text);
LinearGradient lg = new LinearGradient(left, top, left + textW, top, arcColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(lg);
canvas.drawText(text, left, top, mPaint);
}
复制代码
重新onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
// mPaint.setColor(Color.WHITE);
// mPaint.setStrokeWidth(1);
// canvas.drawLine(0, height / 2, width, height / 2, mPaint);
// canvas.drawLine(width / 2, 0, width / 2, height, mPaint);
// canvas.translate(0, height / 5);
// canvas.save();
drawArc(canvas);
drawPoint(canvas);
// canvas.restore();
drawText(canvas);
drawMoneyText(canvas);
}
复制代码
onDraw
方法里只需要依次调用写好的绘制方法就行了。注释掉的代码是调试用的,画了个坐标轴哈哈本来想删了,想了下留着吧。忽略它就行了。
画好了之后就该让它动起来了。决定采用属性动画来做。
ValueAnimator va = ValueAnimator.ofFloat(numberText);
va.setDuration(animTime);
va.setInterpolator(new DecelerateInterpolator());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currValue = (float) animation.getAnimatedValue();
numberText = (int) currValue;
invalidate();
}
});
复制代码
这样就让数字动起来了,其他的呢跟这一样的,只是属性值换一个啦。 然后背景色渐变是用到了ArgbEvaluator
。看代码:
backColorAnim = ValueAnimator.ofInt(backColors);
backColorAnim.setDuration(animTime);
backColorAnim.setEvaluator(new ArgbEvaluator());
backColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
backColor = (int) animation.getAnimatedValue();
//invalidate();
}
});
复制代码
这个类是系统提供关于颜色计算的一个类,有兴趣的可以深入了解一下。
最后定义一些属性,方便使用
<resources>
<declare-styleable name="YbbView">
<attr name="arcWidth" format="dimension" />
<attr name="pointArc" format="integer" />
<attr name="pointSpacing" format="integer" />
<attr name="pointWidth" format="dimension" />
<attr name="maxAmt" format="integer" />
<attr name="animTime" format="integer" />
<attr name="amtTextSize" format="dimension" />
<attr name="tipTextSize" format="dimension" />
</declare-styleable>
</resources>
public YbbView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.YbbView);
metrics = getResources().getDisplayMetrics();
//弧形的宽
arcWidth = typedArray.getDimension(R.styleable.YbbView_arcWidth, TypeValueUtil.dp2px(metrics, 8));
//刻度的宽
pointWidth = typedArray.getDimension(R.styleable.YbbView_pointWidth, TypeValueUtil.dp2px(metrics, 5));
//刻度多长
pointLength = typedArray.getInteger(R.styleable.YbbView_pointArc, 1);
//刻度之间留白
nullLength = typedArray.getInteger(R.styleable.YbbView_pointSpacing, 3);
//数字
numberText = typedArray.getInteger(R.styleable.YbbView_maxAmt, 500000);
//提示文字
tipTextSize = typedArray.getDimension(R.styleable.YbbView_tipTextSize, TypeValueUtil.sp2px(metrics, 12));
//数字的文字大小
numTextSize = typedArray.getDimension(R.styleable.YbbView_amtTextSize, TypeValueUtil.sp2px(metrics, 30));
//动画的时间
animTime = typedArray.getInteger(R.styleable.YbbView_animTime, 1000);
}
复制代码
结束
emmm 可能有些粗糙,不过这么写下来也是学到了很多知识。希望有大牛能多指正我的不足之处。多交流学习。 最后上 Github