时间:2021-01-05 13:54:28 | 栏目:Android代码 | 点击:次
小编最近发现,一些炫酷的view效果,通过需要自定义view和属性动画结合在一起,才能更容易的实现。
实现的效果图如下:
所用的知识有:
(1)自定义View中的 path ,主要用来绘制指示块。
(2)属性动画-ValueAnimator,并设置属性动画的监听器。
(3)根据属性动画是否结束的标志,决定是否绘制分数对应的描述文本内容。
实现步骤:
继承自View,在构造函数中获取自定义属性和初始化操作(初始化画笔)
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; }
覆盖 onSizeChanged() ,得到控件的宽高,并决定要绘制区域的大小(控件默认设置了内边距)
@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; //绘制园的半径是宽高除去默认内边距 }
核心绘制代码,覆盖onDraw()方法,根据动画是否结束的标志,决定是否绘制分数对应的文本。
@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(); } }