Android自定义View实战之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView

【Android自定义View实战之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView】幽映每白日,清辉照衣裳。这篇文章主要讲述Android自定义View实战之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView相关的知识,希望能为你提供帮助。

转载请注明出处: http://blog.csdn.net/linglongxin24/article/details/52936609 【Dylanandroid的csdn博客】
在之前的Android超精准计步器开发-Dylan计步中的首页用到了一个自定义控件, 和QQ运动的界面有点类似, 还有动画效果, 下面就来讲一下这个View是如何绘制的。
1.先看效果图
Android自定义View实战之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView

文章图片

2.效果图分析
  • 功能说明: 黄色的代表用户设置的总计划锻炼步数, 红色的代表用户当前所走的步数。
  • 初步分析: 完全自定义View重写onDraw()方法, 画圆弧。
3.画一个圆弧必备知识 在Canvas中有一个画圆弧的方法
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,

参数一是RectF对象, 一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,
参数二是起始角(度)在电弧的开始, 圆弧起始角度, 单位为度。
参数三圆弧扫过的角度, 顺时针方向, 单位为度,从右中间开始为零度。
参数四是如果是true(真)的话,在绘制圆弧时将圆心包括在内, 通常用来绘制扇形; 如果是false(假)这将是一个弧线。
参数五是Paint对象;
对于这个方法, 大家可以看一下我手绘的草图, 比较烂, 表达一下这几个参数的意思和绘制过程, 画得不好望大家见谅!
Android自定义View实战之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView

文章图片

4.绘图的准备工作 (1).获取中心点坐标
/**中心点的x坐标*/ float centerX = (getWidth()) / 2;

(2).建立一个圆弧外的参考矩形
/**指定圆弧的外轮廓矩形区域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth);

5.绘图的主要步骤 (1).【第一步】绘制整体的黄色圆弧
/** * 1.绘制总步数的黄色圆弧 * * @ param canvas 画笔 * @ param rectF参考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默认画笔颜色, 黄色 */ paint.setColor(getResources().getColor(R.color.yellow)); /** 结合处为圆弧*/ paint.setStrokeJoin(Paint.Join.ROUND); /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/ paint.setStrokeCap(Paint.Cap.ROUND); /** 设置画笔的填充样式 Paint.Style.FILL:填充内部; Paint.Style.FILL_AND_STROKE: 填充内部和描边; Paint.Style.STROKE: 仅描边*/ paint.setStyle(Paint.Style.STROKE); /**抗锯齿功能*/ paint.setAntiAlias(true); /**设置画笔宽度*/ paint.setStrokeWidth(borderWidth); /**绘制圆弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧, 参数一是RectF对象, 一个矩形区域椭圆形的界限用于定义在形状、大小、电弧, 参数二是起始角(度)在电弧的开始, 圆弧起始角度, 单位为度。 参数三圆弧扫过的角度, 顺时针方向, 单位为度,从右中间开始为零度。 参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内, 通常用来绘制扇形; 如果它是false(假)这将是一个弧线, 参数五是Paint对象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); }

(2).【第二步】绘制当前进度的红色圆弧
/** * 2.绘制当前步数的红色圆弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.ROUND); paintCurrent.setStrokeCap(Paint.Cap.ROUND); //圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE); //设置填充样式 paintCurrent.setAntiAlias(true); //抗锯齿功能 paintCurrent.setStrokeWidth(borderWidth); //设置画笔宽度 paintCurrent.setColor(getResources().getColor(R.color.red)); //设置画笔颜色 canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); }

(3).【第三步】绘制当前进度的红色数字
/** * 3.圆环中心的步数 */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true); //抗锯齿功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font); //字体风格 vTextPaint.setColor(getResources().getColor(R.color.red)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); }

(4).【第四步】绘制”步数”的红色数字
/** * 4.圆环中心[步数]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true); //抗锯齿功能 vTextPaint.setColor(getResources().getColor(R.color.grey)); String stepString = " 步数" ; Rect bounds = new Rect(); vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds); canvas.drawText(stepString, centerX, getHeight() / 2 + bounds.height() + getFontHeight(numberTextSize), vTextPaint); }

6.动画是如何实现的-> ValueAnimator ValueAnimator是整个属性动画机制当中最核心的一个类, 属性动画的运行机制是通过不断地对值进行操作来实现的, 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, 我们只需要将初始值和结束值提供给ValueAnimator, 并且告诉它动画所需运行的时长, 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。
/*为进度设置动画 * @ param start 初始值 * @ param current 结束值 * @ param length 动画时长 */ private void setAnimation(float start, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(start, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @ Override public void onAnimationUpdate(ValueAnimator animation) { /**每次在初始值和结束值之间产生的一个平滑过渡的值, 逐步去更新进度*/ currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); }

7.整个自定义StepArcView的源码
import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.AttributeSet; import android.view.View; import cn.bluemobi.dylan.step.R; /** * Created by DylanAndroid on 2016/5/26. * 显示步数的圆弧 */ public class StepArcView extends View {/** * 圆弧的宽度 */ private float borderWidth = 38f; /** * 画步数的数值的字体大小 */ private float numberTextSize = 0; /** * 步数 */ private String stepNumber = " 0" ; /** * 开始绘制圆弧的角度 */ private float startAngle = 135; /** * 终点对应的角度和起始点对应的角度的夹角 */ private float angleLength = 270; /** * 所要绘制的当前步数的红色圆弧终点到起点的夹角 */ private float currentAngleLength = 0; /** * 动画时长 */ private int animationLength = 3000; public StepArcView(Context context) { super(context); }public StepArcView(Context context, AttributeSet attrs) { super(context, attrs); }public StepArcView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }@ Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /**中心点的x坐标*/ float centerX = (getWidth()) / 2; /**指定圆弧的外轮廓矩形区域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth); /**【第一步】绘制整体的黄色圆弧*/ drawArcYellow(canvas, rectF); /**【第二步】绘制当前进度的红色圆弧*/ drawArcRed(canvas, rectF); /**【第三步】绘制当前进度的红色数字*/ drawTextNumber(canvas, centerX); /**【第四步】绘制" 步数" 的红色数字*/ drawTextStepString(canvas, centerX); }/** * 1.绘制总步数的黄色圆弧 * * @ param canvas 画笔 * @ param rectF参考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默认画笔颜色, 黄色 */ paint.setColor(getResources().getColor(R.color.yellow)); /** 结合处为圆弧*/ paint.setStrokeJoin(Paint.Join.ROUND); /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/ paint.setStrokeCap(Paint.Cap.ROUND); /** 设置画笔的填充样式 Paint.Style.FILL:填充内部; Paint.Style.FILL_AND_STROKE: 填充内部和描边; Paint.Style.STROKE: 仅描边*/ paint.setStyle(Paint.Style.STROKE); /**抗锯齿功能*/ paint.setAntiAlias(true); /**设置画笔宽度*/ paint.setStrokeWidth(borderWidth); /**绘制圆弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧, 参数一是RectF对象, 一个矩形区域椭圆形的界限用于定义在形状、大小、电弧, 参数二是起始角(度)在电弧的开始, 圆弧起始角度, 单位为度。 参数三圆弧扫过的角度, 顺时针方向, 单位为度,从右中间开始为零度。 参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内, 通常用来绘制扇形; 如果它是false(假)这将是一个弧线, 参数五是Paint对象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); }/** * 2.绘制当前步数的红色圆弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.ROUND); paintCurrent.setStrokeCap(Paint.Cap.ROUND); //圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE); //设置填充样式 paintCurrent.setAntiAlias(true); //抗锯齿功能 paintCurrent.setStrokeWidth(borderWidth); //设置画笔宽度 paintCurrent.setColor(getResources().getColor(R.color.red)); //设置画笔颜色 canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); }/** * 3.圆环中心的步数 */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true); //抗锯齿功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font); //字体风格 vTextPaint.setColor(getResources().getColor(R.color.red)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); }/** * 4.圆环中心[步数]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true); //抗锯齿功能 vTextPaint.setColor(getResources().getColor(R.color.grey)); String stepString = " 步数" ; Rect bounds = new Rect(); vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds); canvas.drawText(stepString, centerX, getHeight() / 2 + bounds.height() + getFontHeight(numberTextSize), vTextPaint); }/** * 获取当前步数的数字的高度 * * @ param fontSize 字体大小 * @ return 字体高度 */ public int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Rect bounds_Number = new Rect(); paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); return bounds_Number.height(); }/** * dip 转换成px * * @ param dip * @ return */private int dipToPx(float dip) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f * (dip > = 0 ? 1 : -1)); }/** * 所走的步数进度 * * @ param totalStepNum设置的步数 * @ param currentCounts 所走步数 */ public void setCurrentCount(int totalStepNum, int currentCounts) { stepNumber = currentCounts + " " ; setTextSize(currentCounts); /**如果当前走的步数超过总步数则圆弧还是270度, 不能成为园*/ if (currentCounts > totalStepNum) { currentCounts = totalStepNum; } /**所走步数占用总共步数的百分比*/ float scale = (float) currentCounts / totalStepNum; /**换算成弧度最后要到达的角度的长度--> 弧长*/ float currentAngleLength = scale * angleLength; /**开始执行动画*/ setAnimation(0, currentAngleLength, animationLength); }/** * 为进度设置动画 * ValueAnimator是整个属性动画机制当中最核心的一个类, 属性动画的运行机制是通过不断地对值进行操作来实现的, * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, * 我们只需要将初始值和结束值提供给ValueAnimator, 并且告诉它动画所需运行的时长, * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 * * @ param last * @ param current */ private void setAnimation(float last, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @ Override public void onAnimationUpdate(ValueAnimator animation) { currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); }/** * 设置文本大小,防止步数特别大之后放不下, 将字体大小动态设置 * * @ param num */ public void setTextSize(int num) { String s = String.valueOf(num); int length = s.length(); if (length < = 4) { numberTextSize = dipToPx(50); } else if (length > 4 & & length < = 6) { numberTextSize = dipToPx(40); } else if (length > 6 & & length < = 8) { numberTextSize = dipToPx(30); } else if (length > 8) { numberTextSize = dipToPx(25); } }}

8.用法说明
  • xml中
< cn.bluemobi.dylan.step.view.StepArcView android:id= " @ + id/sv " android:layout_width= " 200dp" android:layout_height= " 200dp" android:layout_centerHorizontal= " true" android:layout_marginTop= " 50dp" />

  • Activity中
StepArcViewsv = (StepArcView) findViewById(R.id.sv); sv.setCurrentCount(7000, 1000);


    推荐阅读