写到这里已经写了七篇的自定义View的文章,前段时间朋友让帮忙绘制一个类似仪表盘形状的View,先看整个View的形状有两个圆环,和一个弧,还有线状的指针。
- 首先添加需要定义的属性。
在values下新建一个attr文件添加需要设置属性
<attr name="innerCircleColor" format="color" />
<attr name="innerCircleSize" format="dimension" />
<attr name="outCircleSize" format="dimension" />
<attr name="smallCircleSize" format="dimension" />
<attr name="smallCircleRadiousSize" format="dimension"/>
<declare-styleable name="CircleView">
<attr name="innerCircleColor" />
<attr name="innerCircleSize" />
<attr name="outCircleSize" />
<attr name="smallCircleSize" />
<attr name="smallCircleRadiousSize"/>
</declare-styleable>
innerCircleColor是内部大圆的颜色,因为整个View只是单一的颜色就单设置了一个颜色。innerCircleSize是内部大圆的宽度,outCircleSize是外部圆弧的宽度,smallCircleRadiousSize是外部空心圆的半径,smallCircleSize是内部实心圆与外部空心圆的距离。
2. 获取自定义的属性。
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int arr = a.getIndex(i);
switch (arr) {
case R.styleable.CircleView_innerCircleColor:
innerColor = a.getColor(arr, Color.BLUE);
break;
case R.styleable.CircleView_innerCircleSize:
innerCircleSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
break;
case R.styleable.CircleView_outCircleSize:
outCircleSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
break;
case R.styleable.CircleView_smallCircleSize:
outSmallCircleSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
break;
case R.styleable.CircleView_smallCircleRadiousSize:
outSmallCircleRadiousSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
init();
在init方法里面初始化了画笔和画弧需要的矩形。
private void init() {
mInnerPaint = new Paint();
mOutArcPaint = new Paint();
mOutCirclePaint = new Paint();
linePaint = new Paint();
mOutRectf = new RectF();
}
3.然后重写onMeasure方法。
都是一样的套路,没什么好说的直接贴代码了。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = getPaddingLeft() + getPaddingRight() + (int) mOutRectf.width();
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = getPaddingBottom() + getPaddingTop() + (int) mOutRectf.height();
}
setMeasuredDimension(width, height);
}
4.最后是onDraw方法里面的具体绘制
@Override
protected void onDraw(Canvas canvas) {
int center = getWidth() / 2;
canvas.rotate(-45, center, center);
LinearGradient linearGradient;
if (unitDrgee < 90) {
linearGradient = new LinearGradient(0, 0,getMeasuredWidth(), getMeasuredHeight(), gradientColorArray, null, Shader.TileMode.CLAMP);
} else {
linearGradient = new LinearGradient(0, 0, getMeasuredWidth(), getMeasuredHeight(), gradientColorArrayother, null, Shader.TileMode.CLAMP);
}
mInnerPaint.setShader(linearGradient);
/**
* 先画内圆
*/
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStyle(Paint.Style.STROKE);
mInnerPaint.setStrokeWidth(innerCircleSize);
int radious = center - outSmallCircleRadiousSize * 2 - innerCircleSize / 2;
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radious, mInnerPaint);
/**
* 外圆
*/
mOutArcPaint.setShader(linearGradient);
mOutArcPaint.setAntiAlias(true);
mOutArcPaint.setStrokeWidth(outCircleSize);
mOutArcPaint.setStyle(Paint.Style.STROKE);
mOutRectf.left = outSmallCircleRadiousSize;
mOutRectf.top = outSmallCircleRadiousSize;
mOutRectf.right = getWidth() - outSmallCircleRadiousSize;
mOutRectf.bottom = getHeight() - outSmallCircleRadiousSize;
mOutArcPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawArc(mOutRectf, 180, unitDrgee, false, mOutArcPaint);
/**
* 画刻度线
*/
linePaint.setColor(Color.WHITE);
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.FILL);
linePaint.setStrokeWidth(5);
canvas.drawLine(outSmallCircleRadiousSize * 2, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
/**
* 画小圆
*/
mOutCirclePaint.setShader(linearGradient);
mOutCirclePaint.setAntiAlias(true);
mOutCirclePaint.setStyle(Paint.Style.STROKE);
mOutCirclePaint.setStrokeWidth(10);
float[] r = calculateBallCenter();
canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize, mOutCirclePaint);
mOutCirclePaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize - outSmallCircleSize, mOutCirclePaint);
float angle = 0;
while (angle < unitDrgee) {
canvas.drawLine(outSmallCircleRadiousSize * 2 + 10, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
canvas.rotate(5, center, center);
angle += 5;
}
canvas.drawLine(outSmallCircleRadiousSize * 2, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
}
首先调用了 canvas.rotate(-45, center, center)的方法,为了使外层圆弧的位置在135度处,这里使画布旋转了-45度角。然后设置了整个View的绘制渐变颜色使用LinearGradient线性渐变的Api。
if (unitDrgee < 90) {
linearGradient = new LinearGradient(0, 0, getMeasuredWidth(), getMeasuredHeight(), gradientColorArray, null, Shader.TileMode.CLAMP);
} else {
linearGradient = new LinearGradient(0, 0, getMeasuredWidth(), getMeasuredHeight(), gradientColorArrayother, null, Shader.TileMode.CLAMP);
}
mInnerPaint.setShader(linearGradient);
,然后画内圆。
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStyle(Paint.Style.STROKE);
mInnerPaint.setStrokeWidth(innerCircleSize);
int radious = center - outSmallCircleRadiousSize * 2 - innerCircleSize / 2;
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radious, mInnerPaint);
然后画外层圆弧的位置:
mOutArcPaint.setShader(linearGradient);
mOutArcPaint.setAntiAlias(true);
mOutArcPaint.setStrokeWidth(outCircleSize);
mOutArcPaint.setStyle(Paint.Style.STROKE);
mOutRectf.left = outSmallCircleRadiousSize;
mOutRectf.top = outSmallCircleRadiousSize;
mOutRectf.right = getWidth() - outSmallCircleRadiousSize;
mOutRectf.bottom = getHeight() - outSmallCircleRadiousSize;
mOutArcPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawArc(mOutRectf, 180, unitDrgee, false, mOutArcPaint);
然后画刻度线:
linePaint.setColor(Color.WHITE);
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.FILL);
linePaint.setStrokeWidth(5);
canvas.drawLine(outSmallCircleRadiousSize * 2, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
然后分别画外层的大圆和内部的小圆:
/**
* 画小圆
*/
mOutCirclePaint.setShader(linearGradient);
mOutCirclePaint.setAntiAlias(true);
mOutCirclePaint.setStyle(Paint.Style.STROKE);
mOutCirclePaint.setStrokeWidth(10);
float[] r = calculateBallCenter();
canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize, mOutCirclePaint);
mOutCirclePaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize - outSmallCircleSize, mOutCirclePaint);
再画外部圆的时候,首先根据旋转的弧度确定圆心的位置:
/**
* 计算外圆圆心位置
*
* @return
*/
private float[] calculateBallCenter() {
float[] center = new float[2];
float dy = (float) (Math.sin(Math.toRadians(unitDrgee)) * (getWidth() / 2 - outSmallCircleRadiousSize));
float dx = (float) (Math.cos(Math.toRadians(unitDrgee)) * (getWidth() / 2 - outSmallCircleRadiousSize));
center[0] = getWidth() / 2 - dx;
center[1] = getWidth() / 2 - dy;
return center;
}
最后便是通过不断的旋转弧度循环画刻度线:
float angle = 0;
while (angle < unitDrgee) {
canvas.drawLine(outSmallCircleRadiousSize * 2 + 10, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
canvas.rotate(5, center, center);
angle += 5;
}
基本的都绘制完之后,这里通过属性动画的完成度做了一个加载动画。:
private void setAnimation() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
percentage = progress * value;
if (addUpdateListener != null) {
addUpdateListener.onAddUpdateListener(value);
}
updateProgress();
}
});
valueAnimator.start();
}
基本重要的代码都说完了,觉得好的可以看下源码
本文详细介绍了一种自定义仪表盘View的绘制方法,包括定义属性、获取属性、重写onMeasure方法及具体绘制过程等内容。
1405

被折叠的 条评论
为什么被折叠?



