计步器UI实现

计步器UI实现_第1张图片
目标UI
UI说明:

1.浅色的表示总共是100%
2.深蓝色的表示执行了75%
3.数字描述
4.单位描述

技术知识:

在Canvas中有一个画圆弧的方法

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧

参数一:是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,
参数二:是起始角(度)在电弧的开始,圆弧起始角度,单位为度。
参数三:圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。
参数四:是如果是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果是false(假)这将是一个弧线。
参数五:是Paint对象;

![Canvas中的绘制方法](https://upload-images.jianshu.io/upload_images/6317847-cf1a1bf6a18ccb19.png?
imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

实现步骤:

1.继承自View
在ActionRateView extends View中onMeasure确定View的宽高:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int minimumWidth = getSuggestedMinimumWidth();
//        final int minimumHeight = getSuggestedMinimumHeight();
        int width = getDefaultSize(minimumWidth, widthMeasureSpec);
        int height = width * 46 / 100;  //取宽度的46%作为高度
        setMeasuredDimension(widthMeasureSpec, height);
    }

2.onDraw绘制

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float width = getWidth();
        float centerX = width / 2;
        float left = width * mMarginLeftRadius;
        float top = width * mMarginTopRadius;
        float right = width * (1 - mMarginLeftRadius);
        float bottom = top + (right - left);
        mCircularDiameter = right - left;
        mUnitOfDescriptionSize = width * mUnitOfDescriptionSizeRate;
        mDescriptionSize = width * mDescriptionSizeRate;

        RectF rectF = new RectF(left, top, right, bottom);  //计算绘制图形的区域大小
        /**【第一步】绘制小圆*/
        drawArcMiniCircle(canvas, rectF);
        /**【第二步】绘制当前进度的粗圆弧*/
        drawArcMaxRate(canvas, rectF);
        /**【第四步】绘制单位文字*/
        drawTextStepString(canvas, centerX);
        /**【第三步】绘制核卡率数字*/
        drawTextNumber(canvas, centerX);

    }

3.画小圆

 private void drawArcMiniCircle(Canvas canvas, RectF rectF) {

        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.colorMiniRate)); /** 默认画笔颜色 */
        /** 结合处为圆弧*/
//        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(mMiniRateWidth);/**设置画笔宽度*/
//        canvas.drawArc(rectF, mStartAngle, mAngleLength, false, paint);
        float radius = (rectF.bottom - rectF.top) / 2; //小圆半径
        canvas.drawCircle(rectF.centerX(), rectF.centerY(), radius, paint);//画圆

    }

4.画大圆弧

 private void drawArcMaxRate(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(mMaxRateWidth);//设置画笔宽度
        paintCurrent.setColor(getResources().getColor(R.color.colorMaxRate));//设置画笔颜色

        /**绘制圆弧的方法
         * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,
         参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,
         参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。
         参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。
         参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线,
         参数五是Paint对象;
         */
        canvas.drawArc(rectF, mStartAngle, mCurrentAngleLength, false, paintCurrent);
    }

5.绘制文字单位

 private void drawTextStepString(Canvas canvas, float centerX) {
        Paint vTextPaint = new Paint();
//        vTextPaint.setTextSize(dipToPx(16));
        vTextPaint.setTextSize(mUnitOfDescriptionSize);
        vTextPaint.setTextAlign(Paint.Align.CENTER);
        vTextPaint.setAntiAlias(true);//抗锯齿功能
        vTextPaint.setColor(getResources().getColor(R.color.colorMaxRate));
        Rect bounds = new Rect();
        vTextPaint.getTextBounds(mUnitOfDescription, 0, mUnitOfDescription.length(), bounds);
        Rect bounds_Number = new Rect();
        vTextPaint.getTextBounds(mDescription, 0, mDescription.length(), bounds_Number);
        canvas.drawText(mUnitOfDescription, centerX, getHeight() / 2 + bounds.height() + bounds.height() / 2, vTextPaint);

    }

5.绘制数字


    private void drawTextNumber(Canvas canvas, float centerX) {

        Paint vTextPaint = new Paint();
        vTextPaint.setTextAlign(Paint.Align.CENTER);
        vTextPaint.setAntiAlias(true);//抗锯齿功能
        vTextPaint.setTextSize(mDescriptionSize);
        Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
        vTextPaint.setTypeface(font);//字体风格
        vTextPaint.setColor(getResources().getColor(R.color.colorMaxRate));
        Rect bounds_Number = new Rect();
        vTextPaint.getTextBounds(mDescription, 0, mDescription.length(), bounds_Number);
        canvas.drawText(mDescription, centerX, getHeight() / 2, vTextPaint);

    }

6.添加动画

public void setCurrentCount(int totalStepNum, int currentCounts) {
        mDescription = currentCounts + "";
        mDescriptionTemp = currentCounts;
        if (currentCounts > totalStepNum) {
            currentCounts = totalStepNum;
        }

        float scale = (float) currentCounts / totalStepNum;
//        /**换算成弧度最后要到达的角度的长度-->弧长*/
        float currentAngleLength = scale * mAngleLength;
        /**开始执行动画*/
        setAnimation(0, currentAngleLength, mAnimationTime);
    }

  private void setAnimation(float last, final float current, int length) {
        ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current);
        progressAnimator.setDuration(length);
        progressAnimator.setTarget(mCurrentAngleLength);
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentAngleLength = (float) animation.getAnimatedValue();
                float temp = mDescriptionTemp * (mCurrentAngleLength / current);
                mDescription = temp >= 10 ? temp >= 100 ? (temp + "").substring(0, 3) : (temp + "").substring(0, 2) : (temp + "").substring(0, 1);//String.valueOf(mCurrentAngleLength);
                invalidate();//重新绘制页面
            }

        });
        progressAnimator.start();
    }

7.在xml中添加自定义VIew

        

8.代码中调用

 achievement_arv.setCurrentCount(100, 68)

完整代码:

package com.cmbc.creditcard.cmms.view;

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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.cmbc.creditcard.cmms.R;

/**
 * Created by Aqua on 2018-09-12.
 *
 * @author Isan
 * @version 3.2.1
 * @date 2018-09-12    09:47
 * @api 6
 */

public class ActionRateView extends View {


    private float mMiniRateWidth = 5f;  //细圆的宽度
    private float mMaxRateWidth = 20f;  //大圆的宽度
    private float mMaxTextSize = 12f;  //大字体的大小
    private float mMiniTextSize = 10f; //小字体的大小
    private float mCircularDiameter = 0.0f; //圆的直径
    private float mMarginTopRadius = 0.03f;//圆距离顶端和底端的距离3%
    private float mMarginLeftRadius = 0.3f;//圆距离顶端和底端的距离30%

    private float mStartAngle = 270; //圆开始的角度
    private float mAngleLength = 360; //整个原型的的夹角度
    private float mCurrentAngleLength = 0.0f;//外层圆的夹角

    private int mAnimationTime = 3000;//动画的时常


    private String mDescription = "0";
    private int mDescriptionTemp = 0;
    private String mUnitOfDescription = "核卡率(%)";
//    private String mUnitOfDescription = "XXXXX(%)";
    private float mUnitOfDescriptionSize = 24.0f;  //字体
    private float mUnitOfDescriptionSizeRate = 0.04f;  //字体与屏幕宽度的计算率
    private float mDescriptionSize = 32.0f;  //字体
    private float mDescriptionSizeRate = 0.12f;  //字体


    public ActionRateView(Context context) {
        super(context);
    }

    public ActionRateView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ActionRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float width = getWidth();
        float centerX = width / 2;
        float left = width * mMarginLeftRadius;
        float top = width * mMarginTopRadius;
        float right = width * (1 - mMarginLeftRadius);
        float bottom = top + (right - left);
        mCircularDiameter = right - left;
        mUnitOfDescriptionSize = width * mUnitOfDescriptionSizeRate;
        mDescriptionSize = width * mDescriptionSizeRate;

        RectF rectF = new RectF(left, top, right, bottom);
        /**【第一步】绘制小圆*/
        drawArcMiniCircle(canvas, rectF);
        /**【第二步】绘制当前进度的粗圆弧*/
        drawArcMaxRate(canvas, rectF);
        /**【第四步】绘制单位文字*/
        drawTextStepString(canvas, centerX);
        /**【第三步】绘制XXX率数字*/
        drawTextNumber(canvas, centerX);

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int minimumWidth = getSuggestedMinimumWidth();
//        final int minimumHeight = getSuggestedMinimumHeight();
        int width = getDefaultSize(minimumWidth, widthMeasureSpec);
        int height = width * 46 / 100;  //取宽度的46%作为高度
        setMeasuredDimension(widthMeasureSpec, height);
    }

    private void drawArcMiniCircle(Canvas canvas, RectF rectF) {

        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.colorMiniRate)); /** 默认画笔颜色 */
        /** 结合处为圆弧*/
//        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(mMiniRateWidth);/**设置画笔宽度*/
//        canvas.drawArc(rectF, mStartAngle, mAngleLength, false, paint);
        float radius = (rectF.bottom - rectF.top) / 2; //小圆半径
        canvas.drawCircle(rectF.centerX(), rectF.centerY(), radius, paint);//画圆

    }

    private void drawArcMaxRate(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(mMaxRateWidth);//设置画笔宽度
        paintCurrent.setColor(getResources().getColor(R.color.colorMaxRate));//设置画笔颜色

        /**绘制圆弧的方法
         * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,
         参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,
         参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。
         参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。
         参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线,
         参数五是Paint对象;
         */
        canvas.drawArc(rectF, mStartAngle, mCurrentAngleLength, false, paintCurrent);
    }

    private void drawTextNumber(Canvas canvas, float centerX) {

        Paint vTextPaint = new Paint();
        vTextPaint.setTextAlign(Paint.Align.CENTER);
        vTextPaint.setAntiAlias(true);//抗锯齿功能
        vTextPaint.setTextSize(mDescriptionSize);
        Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
        vTextPaint.setTypeface(font);//字体风格
        vTextPaint.setColor(getResources().getColor(R.color.colorMaxRate));
        Rect bounds_Number = new Rect();
        vTextPaint.getTextBounds(mDescription, 0, mDescription.length(), bounds_Number);
        canvas.drawText(mDescription, centerX, getHeight() / 2, vTextPaint);

    }

    private void drawTextStepString(Canvas canvas, float centerX) {
        Paint vTextPaint = new Paint();
//        vTextPaint.setTextSize(dipToPx(16));
        vTextPaint.setTextSize(mUnitOfDescriptionSize);
        vTextPaint.setTextAlign(Paint.Align.CENTER);
        vTextPaint.setAntiAlias(true);//抗锯齿功能
        vTextPaint.setColor(getResources().getColor(R.color.colorMaxRate));
        Rect bounds = new Rect();
        vTextPaint.getTextBounds(mUnitOfDescription, 0, mUnitOfDescription.length(), bounds);
        Rect bounds_Number = new Rect();
        vTextPaint.getTextBounds(mDescription, 0, mDescription.length(), bounds_Number);
        canvas.drawText(mUnitOfDescription, centerX, getHeight() / 2 + bounds.height() + bounds.height() / 2, vTextPaint);

    }

    /**
     * 获取当前步数的数字的高度
     *
     * @param fontSize 字体大小
     * @return 字体高度
     */
    public int getFontHeight(float fontSize) {
        Paint paint = new Paint();
        paint.setTextSize(fontSize);
        Rect bounds_Number = new Rect();
        paint.getTextBounds(mDescription, 0, mDescription.length(), bounds_Number);
        return bounds_Number.height();
    }


    public void setCurrentCount(int totalNum, int currentCounts) {
        mDescription = currentCounts + "";
        mDescriptionTemp = currentCounts;
        if (currentCounts > totalNum) {
            currentCounts = totalNum;
        }

//        /**所走步数占用总共步数的百分比*/
        float scale = (float) currentCounts / totalNum;
//        /**换算成弧度最后要到达的角度的长度-->弧长*/
        float currentAngleLength = scale * mAngleLength;
        /**开始执行动画*/
        setAnimation(0, currentAngleLength, mAnimationTime);
    }

    /**
     * 为进度设置动画
     * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的,
     * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。
     * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,
     * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,
     * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。
     *
     */
    private void setAnimation(float last, final float current, int length) {
        ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current);
        progressAnimator.setDuration(length);
        progressAnimator.setTarget(mCurrentAngleLength);
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentAngleLength = (float) animation.getAnimatedValue();
                float temp = mDescriptionTemp * (mCurrentAngleLength / current);
                mDescription = temp >= 10 ? temp >= 100 ? (temp + "").substring(0, 3) : (temp + "").substring(0, 2) : (temp + "").substring(0, 1);
                invalidate();//重新绘制页面
            }

        });
        progressAnimator.start();
    }

}

你可能感兴趣的:(计步器UI实现)