自定义View实现运动类app中3个圆弧展示运动数据
来源:互联网 发布:中国幅员辽阔知乎 编辑:程序博客网 时间:2024/04/28 04:31
公司项目要求实现一个用3个圆弧展示运动数据的view,先看下效果图 图片反了,凑合看吧
话不多说,直接看代码
package com.a51lupao.www.myapplication;/** * Created by gaoTz on 2017/2/14. */import android.animation.ValueAnimator;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.graphics.SweepGradient;import android.support.v4.content.ContextCompat;import android.util.AttributeSet;import android.view.View;import java.util.ArrayList;import java.util.List;public class DataRing extends View { private Context context; private static final int DEFAULT_RING_WIDTH = 60; private static final int DEFAULT_DATA_TEXT_SIZE = 8; /** * 圆弧的宽度 */ private float ringWidth; /** * 三个圆环上文字的字体大小 */ private float dataTextSize; /** * 三个圆环对应的RectF集合 */ private RectF[] rectFs; private int drawColor[]; private int drawColorBefore[]; private float startAngle = -90; /** * 终点对应的角度和起始点对应的角度的夹角 */ private float angleLength1 = 0; private float angleLength2 = 0; private float angleLength3 = 0; //圆弧对应的渐变色 private int[] colors1 = new int[]{Color.RED, Color.BLUE, Color.RED}; private int[] colors2 = new int[]{Color.GREEN, Color.RED, Color.GREEN}; private int[] colors3 = new int[]{Color.BLUE, Color.GREEN, Color.BLUE}; private List<int[]> colorList = new ArrayList<>(); public DataRing(Context context) { super(context); this.context = context; } public DataRing(Context context, AttributeSet attrs) { super(context, attrs, 0); this.context = context; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DataRing); ringWidth = typedArray.getDimensionPixelSize(R.styleable.DataRing_ringWidth, DEFAULT_RING_WIDTH); dataTextSize = typedArray.getDimensionPixelSize(R.styleable.DataRing_dataTextSize, DEFAULT_DATA_TEXT_SIZE); typedArray.recycle(); } public DataRing(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; } /** * 初始化3个圆环的RectF */ private void initRectF() { float centerX = (getWidth()) / 2; rectFs = new RectF[3]; for (int i = 1; i < rectFs.length + 1; i++) { rectFs[i - 1] = new RectF(ringWidth * (i), ringWidth * (i), 2 * centerX - ringWidth * i, 2 * centerX - ringWidth * i); } colorList.add(colors1); colorList.add(colors2); colorList.add(colors3); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); initRectF(); /** * 绘制圆弧 * a b c 分别为圆弧头部与起始位置夹角的正弦 * */ double sina = ringWidth / (getWidth() / 2 - ringWidth / 2); double sinb = ringWidth / (getWidth() / 2 - ringWidth / 2 - ringWidth); double sinc = ringWidth / (getWidth() / 2 - ringWidth / 2 - ringWidth * 2); drawArc(canvas, rectFs[0], 0, angleLength1, sina); drawArc(canvas, rectFs[1], 1, angleLength2, sinb); drawArc(canvas, rectFs[2], 2, angleLength3, sinc); drawDataText(canvas, getWidth() / 2, "步频", 0); drawDataText(canvas, getWidth() / 2, "步幅", 1); drawDataText(canvas, getWidth() / 2, "时长", 2); } /** * 2.绘制圆弧 * aSin 圆弧头部与起始位置夹角的弧度 * degree 圆弧头部与起始位置夹角的度数 */ private void drawArc(Canvas canvas, RectF rectF, int position, float currentAngleLength, double a) { float aSin = (float) Math.asin(a); float degree = (float) (aSin * 180 / Math.PI); int count = ((int) currentAngleLength) / 360; Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.ROUND); paintCurrent.setStrokeCap(Paint.Cap.ROUND);//圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//设置填充样式 paintCurrent.setAntiAlias(true);//抗锯齿功能 paintCurrent.setStrokeWidth(ringWidth);//设置画笔宽度 drawColor = colorList.get(position); SweepGradient sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, drawColor, null); Matrix matrix = new Matrix(); matrix.setRotate(startAngle - degree, getWidth() / 2, getHeight() / 2);//填充色起始位置 sweepGradient.setLocalMatrix(matrix); paintCurrent.setShader(sweepGradient); if (count == 0) {//绘制第一圈圆弧 canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); } else { int centerRed = Color.argb(255, 255, 0, 75 * count); int centerRedBefore = Color.argb(255, 255, 0, 75 * (count - 1)); int centerGreen = Color.argb(255, 0, 255, 75 * count); int centerGreenBefore = Color.argb(255, 0, 255, 75 * (count - 1)); int centerBlue = Color.argb(255, 75 * count, 0, 255); int centerBlueBefore = Color.argb(255, 75 * (count - 1), 0, 255); switch (position) { case 0: drawColor = new int[]{Color.RED, centerGreen, Color.RED}; drawColorBefore = new int[]{Color.RED, centerGreenBefore, Color.RED}; break; case 1: drawColor = new int[]{Color.GREEN, centerBlue, Color.GREEN}; drawColorBefore = new int[]{Color.GREEN, centerBlueBefore, Color.GREEN}; break; case 2: drawColor = new int[]{Color.BLUE, centerRed, Color.BLUE}; drawColorBefore = new int[]{Color.BLUE, centerRedBefore, Color.BLUE}; break; } /** * 绘制第二圈圆弧的时候,要先绘制第一圈圆弧,作为底图 以此类推 * 每当绘制第n圈圆弧的时候,首先绘制第n-1圈圆弧作为底图 */ if (count == 1) { sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, colorList.get(position), null); } else { sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, drawColorBefore, null); } matrix = new Matrix(); matrix.setRotate(startAngle - degree, getWidth() / 2, getHeight() / 2);//填充色起始位置 sweepGradient.setLocalMatrix(matrix); paintCurrent.setShader(sweepGradient); canvas.drawArc(rectF, startAngle, currentAngleLength - 360 * (count - 1), false, paintCurrent); sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, drawColor, null); matrix = new Matrix(); matrix.setRotate(startAngle - degree, getWidth() / 2, getHeight() / 2);//填充色起始位置 sweepGradient.setLocalMatrix(matrix); paintCurrent.setShader(sweepGradient); canvas.drawArc(rectF, startAngle, currentAngleLength - 360 * count, false, paintCurrent); } } /** * 4.圆弧开头文字 */ private void drawDataText(Canvas canvas, float centerX, String text, int type) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dataTextSize); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setColor(ContextCompat.getColor(context, R.color.gray)); Rect bounds = new Rect(); vTextPaint.getTextBounds(text, 0, text.length(), bounds); canvas.drawText(text, centerX + bounds.width() / 2, ringWidth + ringWidth * type + bounds.height() / 2, vTextPaint); } /** * 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)); } public void setCurrentCount(int animTime, int total1, int current1, int total2, int current2, int total3, int current3) { float currentAngleLength1 = (float) current1 / total1 * 360; float currentAngleLength2 = (float) current2 / total2 * 360; float currentAngleLength3 = (float) current3 / total3 * 360; /** * 圆弧最多转720度,超过720度按720度显示 */ currentAngleLength1 = currentAngleLength1 >= 720 ? 720 : currentAngleLength1; currentAngleLength2 = currentAngleLength2 >= 720 ? 720 : currentAngleLength2; currentAngleLength3 = currentAngleLength3 >= 720 ? 720 : currentAngleLength3; /**开始执行动画*/ setAnimation(0, currentAngleLength1, animTime); setAnimation1(0, currentAngleLength2, animTime); setAnimation2(0, currentAngleLength3, animTime); } /** * 为进度设置动画 * 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(angleLength1); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { angleLength1 = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); } private void setAnimation1(float last, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current); progressAnimator.setDuration(length); progressAnimator.setTarget(angleLength2); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { angleLength2 = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); } private void setAnimation2(float last, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current); progressAnimator.setDuration(length); progressAnimator.setTarget(angleLength3); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { angleLength3 = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); }}
第一步
新建一个继承自View的类DataRing,在构造方法中初始化控件的自定义属性
自定义属性的配置在values下面新建attrs.xml .不懂的可以自行百度相关内容
初始化构造方法
对于3个构造方法
public DataRing(Context context) { super(context); this.context = context;}
public DataRing(Context context) { super(context); this.context = context;}
public DataRing(Context context, AttributeSet attrs) { super(context, attrs, 0); this.context = context; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DataRing); ringWidth = typedArray.getDimensionPixelSize(R.styleable.DataRing_ringWidth, DEFAULT_RING_WIDTH); dataTextSize = typedArray.getDimensionPixelSize(R.styleable.DataRing_dataTextSize, DEFAULT_DATA_TEXT_SIZE); typedArray.recycle();}
public DataRing(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context;
android开发者网站上有相关的说明文档:
public View (Context context)是在java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
public View (Context context, AttributeSet attrs)这个是在xml创建但是没有指定style的时候被调用
public View (Context context, AttributeSet attrs, int defStyle)给View提供一个基本的style,如果我们没有对View设置某些属性,就使用这个style中的属性。
第二步 重写onDraw方法,绘制View
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); initRectF(); /** * 绘制圆弧 * a b c 分别为圆弧头部与起始位置夹角的正弦 * */ double sina = ringWidth / (getWidth() / 2 - ringWidth / 2); double sinb = ringWidth / (getWidth() / 2 - ringWidth / 2 - ringWidth); double sinc = ringWidth / (getWidth() / 2 - ringWidth / 2 - ringWidth * 2); drawArc(canvas, rectFs[0], 0, angleLength1, sina); drawArc(canvas, rectFs[1], 1, angleLength2, sinb); drawArc(canvas, rectFs[2], 2, angleLength3, sinc); drawDataText(canvas, getWidth() / 2, "步频", 0); drawDataText(canvas, getWidth() / 2, "步幅", 1); drawDataText(canvas, getWidth() / 2, "时长", 2);}
其中initRecF对3个圆弧的RectF进行初始化,RectF用于设定圆弧的大小。参数也很好理解,分别用来限定圆弧的4个位置信息
接下来便是重点 圆弧的绘制
/** * 2.绘制圆弧 * aSin 圆弧头部与起始位置夹角的弧度 * degree 圆弧头部与起始位置夹角的度数 */private void drawArc(Canvas canvas, RectF rectF, int position, float currentAngleLength, double a) { float aSin = (float) Math.asin(a); float degree = (float) (aSin * 180 / Math.PI); int count = ((int) currentAngleLength) / 360; Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.ROUND); paintCurrent.setStrokeCap(Paint.Cap.ROUND);//圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//设置填充样式 paintCurrent.setAntiAlias(true);//抗锯齿功能 paintCurrent.setStrokeWidth(ringWidth);//设置画笔宽度 drawColor = colorList.get(position); SweepGradient sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, drawColor, null); Matrix matrix = new Matrix(); matrix.setRotate(startAngle - degree, getWidth() / 2, getHeight() / 2);//填充色起始位置 sweepGradient.setLocalMatrix(matrix); paintCurrent.setShader(sweepGradient); if (count == 0) {//绘制第一圈圆弧 canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); } else { int centerRed = Color.argb(255, 255, 0, 75 * count); int centerRedBefore = Color.argb(255, 255, 0, 75 * (count - 1)); int centerGreen = Color.argb(255, 0, 255, 75 * count); int centerGreenBefore = Color.argb(255, 0, 255, 75 * (count - 1)); int centerBlue = Color.argb(255, 75 * count, 0, 255); int centerBlueBefore = Color.argb(255, 75 * (count - 1), 0, 255); switch (position) { case 0: drawColor = new int[]{Color.RED, centerGreen, Color.RED}; drawColorBefore = new int[]{Color.RED, centerGreenBefore, Color.RED}; break; case 1: drawColor = new int[]{Color.GREEN, centerBlue, Color.GREEN}; drawColorBefore = new int[]{Color.GREEN, centerBlueBefore, Color.GREEN}; break; case 2: drawColor = new int[]{Color.BLUE, centerRed, Color.BLUE}; drawColorBefore = new int[]{Color.BLUE, centerRedBefore, Color.BLUE}; break; } /** * 绘制第二圈圆弧的时候,要先绘制第一圈圆弧,作为底图 以此类推 * 每当绘制第n圈圆弧的时候,首先绘制第n-1圈圆弧作为底图 */ if (count == 1) { sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, colorList.get(position), null); } else { sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, drawColorBefore, null); } matrix = new Matrix(); matrix.setRotate(startAngle - degree, getWidth() / 2, getHeight() / 2);//填充色起始位置 sweepGradient.setLocalMatrix(matrix); paintCurrent.setShader(sweepGradient); canvas.drawArc(rectF, startAngle, currentAngleLength - 360 * (count - 1), false, paintCurrent); sweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, drawColor, null); matrix = new Matrix(); matrix.setRotate(startAngle - degree, getWidth() / 2, getHeight() / 2);//填充色起始位置 sweepGradient.setLocalMatrix(matrix); paintCurrent.setShader(sweepGradient); canvas.drawArc(rectF, startAngle, currentAngleLength - 360 * count, false, paintCurrent); }}
setStrokeCap用来设置画笔的笔触,也就是笔头的样式
SweepGradient 用来设置圆弧的渐变色 前两个参数为中心点的x,y坐标 ,第三个参数为渐变色的颜色集合,第四个参数用来指定特定位置的颜色显示如果设置为null,则按照colors集合中的颜色均匀渐变。
canvas.drawArc用来绘制圆弧
第一个参数为刚刚初始化的RectF
第二个参数为绘制圆弧的起始位置,按顺时针方向为正,其中0代表右侧水平方向(类似于表盘,0代表数字3的位置 ,本文初始位置设置为-90,即表盘中数字12的位置)
第三个参数 为当前位置与初始位置的夹角度数
第四个参数,如果设置为true,在绘制圆弧时,将圆心包括在内,通常用来绘制扇形
第五个参数 ,画笔
注意
接下来绘制文字。
/** * 4.圆弧开头文字 */private void drawDataText(Canvas canvas, float centerX, String text, int type) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dataTextSize); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setColor(ContextCompat.getColor(context, R.color.gray)); Rect bounds = new Rect(); vTextPaint.getTextBounds(text, 0, text.length(), bounds); canvas.drawText(text, centerX + bounds.width() / 2, ringWidth + ringWidth * type + bounds.height() / 2, vTextPaint);}
接下来 开启动画,大功告成
/** * 为进度设置动画 * 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(angleLength1); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { angleLength1 = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start();}
项目要求的单个圆弧最多只能转2圈,可根据需求自行设定
/** * 圆弧最多转720度,超过720度按720度显示 */currentAngleLength1 = currentAngleLength1 >= 720 ? 720 : currentAngleLength1;currentAngleLength2 = currentAngleLength2 >= 720 ? 720 : currentAngleLength2;currentAngleLength3 = currentAngleLength3 >= 720 ? 720 : currentAngleLength3;
github地址
https://github.com/gaotianzeng/DataCircleView
- 自定义View实现运动类app中3个圆弧展示运动数据
- Android自定义UI实战(基础篇3)---图标圆弧运动
- 自定义view运动
- cocos2d-x实现node圆弧运动
- 自定义View实例(一)----微博运动积分的实现
- 自定义View实例(一)----微博运动积分的实现
- Android自定义一个View实现运动的小人
- 【Android自定义View实战】之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView
- 自定义view实现圆弧进度条
- cocos2d-x实现node圆弧运动 (附源代码)
- 通过webSocket实现app运动数据在网页实时显示
- 【运动控制】运动控制类实现
- 自定义View-仿小米运动 连接效果
- jQuery 运动特效展示
- 运动
- 运动
- android自定义数据展示view,随手指滑动实现
- 对运动类 App 的产品分析
- 为什么要有置顶这个分类
- ubuntu安装软件依赖
- 深度学习工具比较1
- 树莓派离线安装chkconfig
- ContentProvider内容提供者与ContentResolver内容访问者
- 自定义View实现运动类app中3个圆弧展示运动数据
- Activiti架构与组件 (二)
- android上live555获取IP为0.0.0.0的问题
- 深入了解硬盘的读写原理和碎片的产生(图文详解)
- Difference between Business and Matrix
- SpringIOC原理
- openwrt下的网络设置过程
- <hadoop>文件操作
- python 通过pip安装第三方库