自定义View实现运动类app中3个圆弧展示运动数据


公司项目要求实现一个用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

@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);
}

其中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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值