自定义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,在绘制圆弧时,将圆心包括在内,通常用来绘制扇形

第五个参数 ,画笔

注意

由于在绘制圆弧动画的时候,onDraw方法不断调用,则绘制第n圈圆弧的时候,首先要绘制n-1圈圆弧作为底图,不然的话只是绘制当前的圆弧。

接下来绘制文字。

/** * 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

0 0
原创粉丝点击