美文网首页Android自定义View实战首页投稿(暂停使用,暂停投稿)自定义控件
【Android自定义View实战】之仿QQ运动步数圆弧及动画,

【Android自定义View实战】之仿QQ运动步数圆弧及动画,

作者: 亮之于东 | 来源:发表于2016-11-01 11:11 被阅读349次

    转载请注明出处: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);
    

    相关文章

      网友评论

      本文标题:【Android自定义View实战】之仿QQ运动步数圆弧及动画,

      本文链接:https://www.haomeiwen.com/subject/hnlmuttx.html