转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/52936609 【DylanAndroid的csdn博客】

在之前的Android超精准计步器开发-Dylan计步中的首页用到了一个自定义控件,和QQ运动的界面有点类似,还有动画效果,下面就来讲一下这个View是如何绘制的。

1.先看效果图

2.效果图分析

  • 功能说明:黄色的代表用户设置的总计划锻炼步数,红色的代表用户当前所走的步数。
  • 初步分析:完全自定义View重写onDraw()方法,画圆弧。

3.画一个圆弧必备知识

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

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

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

对于这个方法,大家可以看一下我手绘的草图,比较烂,表达一下这几个参数的意思和绘制过程,画得不好望大家见谅!

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中
StepArcView  sv = (StepArcView) findViewById(R.id.sv);sv.setCurrentCount(7000, 1000);

更多相关文章

  1. Android仿百度外卖自定义下拉刷新效果
  2. Android(安卓)吸入动画效果实现分解
  3. 微信小程序菜单实现
  4. 不用线程做Android软件欢迎界面,透明效果,完成后自动跳转
  5. Android(安卓)入门第九讲03-动画(帧动画(点击开始,停止)+补间动画(透
  6. android Paint和Color类介绍
  7. 仿MIUI的Toast动画效果实现
  8. android 组件动画(一)——球的进入效果
  9. AssetManager读取assets下多张图片资源输出到ImageView动画

随机推荐

  1. android intent 隐式意图和显示意图(acti
  2. [置顶] Android 内存优化的几点知识
  3. [置顶] android用户输入系统详细说明
  4. Android假退出不是流氓行为
  5. Android注解式绑定控件,没你想象的那么难
  6. Android中你也许不知道的线性布局Layout_
  7. Android Apk打包过程概述:Android是如何打
  8. activity 的属性android:taskAffinity和a
  9. Qt的Android开发让程序全屏显示
  10. Android 绘图进阶(四):自定义View属性(灰常重