好久没有写自定义View相关的内容,好多东西都忘记干净了,乘着这有时间,赶紧复习下。渐渐发现,一些炫酷的view效果,通过需要自定义view和属性动画结合在一起,才能更容易的实现。
实现的效果图如下:
所用的知识有:
(1)自定义View中的 path ,主要用来绘制指示块。
(2)属性动画-ValueAnimator,并设置属性动画的监听器。
(3)根据属性动画是否结束的标志,决定是否绘制分数对应的描述文本内容。
private void obtainAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScoreView);
lineLength = typedArray.getDimension(R.styleable.ScoreView_lineLength, dp2Px(10));
lineColor = typedArray.getColor(R.styleable.ScoreView_lineColor, Color.WHITE);
typedArray.recycle();
}
private void init() {
arrowPaint = createPaint(Color.WHITE, 0, Paint.Style.FILL, 0);
arcPaint = createPaint(lineColor, dp2Px(1), Paint.Style.STROKE, 0);
bgPaint = createPaint(lineColor, dp2Px(1), Paint.Style.FILL, 0);
reachProgressPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.FILL, 0);
arcReachPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.STROKE, 0);
scoreTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(26));
descTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(16));
}
其中初始化画笔抽取到一个函数中:
private Paint createPaint(int color, int strokeWidth, Paint.Style style, float textSize) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextSize(textSize);
return paint;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
viewHeight = h;
halfView = Math.min(viewWidth, viewHeight) / 2; //宽度或高度中最小值的一半,即决定圆心的位置。
radius = (Math.min(viewWidth, viewHeight) - 2 * DEFAULT_PADDING) / 2; //绘制园的半径是宽高除去默认内边距
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArcBackground(canvas);
drawArcProgress(canvas);
drawScoreText(canvas);
if (isAnimEnd) {
drawDescText(canvas);
}
}
(1)绘制圆弧背景和灰色刻度背景。
private void drawArcBackground(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//绘制圆弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, 300, false, arcPaint);
//绘制刻度线
canvas.rotate(30);
for (int i = 0; i < 100; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
bgPaint);
canvas.rotate(degree);
}
canvas.restore();
}
(2) 绘制刻度,根据ValueAnimator进行动画的当前值curValue,来动态改变绘制指示块和已达进度圆弧,从而实现从0开始移动到设定值的动画效果。
private void drawArcProgress(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//绘制圆弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, curValue * degree, false, arcReachPaint);
//绘制指示方块,方块是从0开始移动某一个位置的
canvas.rotate(30 + degree * curValue);
Path path = new Path();
path.moveTo(dp2Px(5), radius);
path.lineTo(dp2Px(5), radius - dp2Px(10));
path.lineTo(0, radius - dp2Px(15));
path.lineTo(-dp2Px(5), radius - dp2Px(10));
path.lineTo(-dp2Px(5), radius);
path.close();
canvas.drawPath(path, arrowPaint);
//绘制已经达到的刻度
canvas.restore();
canvas.save();
canvas.translate(halfView, halfView);
canvas.rotate(30);
for (int i = 0; i < curValue; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
reachProgressPaint);
canvas.rotate(degree);
}
canvas.restore();
}
(3) 绘制分数文本。
private void drawScoreText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String scoreText = curValue + "分";
float textLength = scoreTextPaint.measureText(scoreText);
canvas.drawText(scoreText, -textLength / 2, 0, scoreTextPaint);
canvas.restore();
}
(4) 动画结束时,绘制最终分数对应的提示信息,该信息只有在动画结束后,才会显示出来。
private void drawDescText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String desc = "";
if (curValue >= 90) {
desc = "车神";
} else {
desc = "马路杀手";
}
float descLength = descTextPaint.measureText(desc);
canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint);
canvas.restore();
isAnimEnd = false; // isAnimEnd置为false,防止再次点击start时,就显示动画结束时才能显示的内容
}
(5)提供对外设置最大值的接口,决定最后的分数。
/**
* 对外提供的接口,用于设置进度的最大值
*
* @param value
*/
public void setMaxValue(int value) {
if (valueAnimator == null) {
valueAnimator = ValueAnimator.ofInt(0, value);
}
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(30 * value);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
curValue = (int) animation.getAnimatedValue();
Log.i("debug", "curValue=" + curValue);
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimEnd = true; //标记动画结束
Log.i("debug", "onAnimationEnd");
Log.i("debug", "onAnimationEnd curValue = " + curValue);
invalidate();
}
});
valueAnimator.start();
}
完整代码:
public class ScoreView extends View {
private final int DEFAULT_PADDING = dp2Px(5);
private final int DEFAULT_WIDTH = dp2Px(200); //默认宽度为200dp
private final int DEFAULT_HEIGHT = dp2Px(200); //默认高度为200dp
private int viewWidth; //View宽度
private int viewHeight; //View高度
private int halfView; //view宽度或高度的一半
private int radius; //绘制圆形区域的半径
private Paint bgPaint;
private Paint arrowPaint; //指示块画笔
private Paint arcPaint; //圆弧画笔
private Paint arcReachPaint; //圆弧画笔
private Paint reachProgressPaint; //已达刻度
private Paint scoreTextPaint; //绘制分数文本
private Paint descTextPaint; //绘制描述文本
private float degree = 3f; //相邻刻度之间夹角大小为3度,角度制,不是弧度制
private float lineLength;
private int lineColor;
private int curValue; //动画进行的当前值
private ValueAnimator valueAnimator;
private boolean isAnimEnd = false;
public ScoreView(Context context) {
this(context, null);
}
public ScoreView(Context context, AttributeSet attrs) {
super(context, attrs);
obtainAttrs(context, attrs);
init();
}
private void obtainAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScoreView);
lineLength = typedArray.getDimension(R.styleable.ScoreView_lineLength, dp2Px(10));
lineColor = typedArray.getColor(R.styleable.ScoreView_lineColor, Color.WHITE);
typedArray.recycle();
}
private void init() {
arrowPaint = createPaint(Color.WHITE, 0, Paint.Style.FILL, 0);
arcPaint = createPaint(lineColor, dp2Px(1), Paint.Style.STROKE, 0);
bgPaint = createPaint(lineColor, dp2Px(1), Paint.Style.FILL, 0);
reachProgressPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.FILL, 0);
arcReachPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.STROKE, 0);
scoreTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(26));
descTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(16));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureSize(widthMeasureSpec, DEFAULT_WIDTH), measureSize(heightMeasureSpec, DEFAULT_HEIGHT));
}
private int measureSize(int measureSpec, int defaultSize) {
int measureSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
measureSize = size;
} else {
if (mode == MeasureSpec.AT_MOST) {
measureSize = Math.min(defaultSize, size);
}
}
return measureSize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
viewHeight = h;
halfView = Math.min(viewWidth, viewHeight) / 2; //宽度或高度中最小值的一半,即圆形的位置
radius = (Math.min(viewWidth, viewHeight) - 2 * DEFAULT_PADDING) / 2; //半径是宽高除去默认内边距的
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArcBackground(canvas);
drawArcProgress(canvas);
drawScoreText(canvas);
if (isAnimEnd) {
drawDescText(canvas);
}
}
private void drawScoreText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String scoreText = curValue + "分";
float textLength = scoreTextPaint.measureText(scoreText);
canvas.drawText(scoreText, -textLength / 2, 0, scoreTextPaint);
canvas.restore();
}
/**
* 绘制文本
*
* @param canvas
*/
private void drawDescText(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
String desc = "";
if (curValue >= 90) {
desc = "车神";
} else {
desc = "马路杀手";
}
float descLength = descTextPaint.measureText(desc);
canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint);
canvas.restore();
isAnimEnd = false; // isAnimEnd置为false,防止再次点击start时,就显示动画结束时才能显示的内容
}
/**
* 绘制进度
*
* @param canvas
*/
private void drawArcProgress(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//绘制圆弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, curValue * degree, false, arcReachPaint);
//绘制指示方块,方块是从0开始移动某一个位置的
canvas.rotate(30 + degree * curValue);
Path path = new Path();
path.moveTo(dp2Px(5), radius);
path.lineTo(dp2Px(5), radius - dp2Px(10));
path.lineTo(0, radius - dp2Px(15));
path.lineTo(-dp2Px(5), radius - dp2Px(10));
path.lineTo(-dp2Px(5), radius);
path.close();
canvas.drawPath(path, arrowPaint);
//绘制已经达到的刻度
canvas.restore();
canvas.save();
canvas.translate(halfView, halfView);
canvas.rotate(30);
for (int i = 0; i < curValue; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
reachProgressPaint);
canvas.rotate(degree);
}
canvas.restore();
}
/**
* 绘制背景
*
* @param canvas
*/
private void drawArcBackground(Canvas canvas) {
canvas.save();
canvas.translate(halfView, halfView);
//绘制圆弧
RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
canvas.drawArc(rectF, 120, 300, false, arcPaint);
//绘制刻度线
canvas.rotate(30);
for (int i = 0; i < 100; i++) {
canvas.drawLine(0, radius - dp2Px(15), 0,
radius - dp2Px(15) - lineLength,
bgPaint);
canvas.rotate(degree);
}
canvas.restore();
}
/**
* 创建画笔
*
* @param color
* @param strokeWidth
* @param style
* @param textSize
* @return
*/
private Paint createPaint(int color, int strokeWidth, Paint.Style style, float textSize) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextSize(textSize);
return paint;
}
/**
* dp转换成px
*
* @param dpValue
* @return
*/
private int dp2Px(int dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpValue, getResources().getDisplayMetrics());
}
/**
* 对外提供的接口,用于设置进度的最大值
*
* @param value
*/
public void setMaxValue(int value) {
if (valueAnimator == null) {
valueAnimator = ValueAnimator.ofInt(0, value);
}
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(30 * value);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
curValue = (int) animation.getAnimatedValue();
Log.i("debug", "curValue=" + curValue);
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimEnd = true; //标记动画结束
Log.i("debug", "onAnimationEnd");
Log.i("debug", "onAnimationEnd curValue = " + curValue);
invalidate();
}
});
valueAnimator.start();
}
}