自定义View(三),仿小米运动计步

前面主要说了自定义View的一些知识,这篇文章主要是利用自定义View做一个仿小米运动计步功能的控件,如下图所示:


自定义View(三),仿小米运动计步_第1张图片

分析一下思路:
1.画背景
2.画一个最外部的圆
3.画圆上的小圆点
4.画竖线,环绕一周
5.画圆环
6.画文字
7.添加动画
为了可以自定义各个控件的显示效果,自定义View的属性还是必要的。

自定义属性

自定义属性主要是自定义了各个部件的颜色,format是该属性的取值类型。
这里要注意的是,自定义属性的name定义成了XiaoMiStep,那么自定义View的名字也要是XiaoMiStep,保持一致。


        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

然后就是在布局文件中申明我们的自定义view。
这样,自定义View XiaoMiStep在xml布局文件里引用的时候,代码如下:


记得最后要引入我们的命名空间
xmlns:custom="http://schemas.android.com/apk/res-auto" 引入我们自定义的属性

获得atts.xml定义的属性值

自定义View一般需要实现一下三个构造方法,这三个构造方法是一层调用一层的,属于递进关系。因此,我们只需要在最后一个构造方法中来获得View的属性了。

  1. 通过theme.obtainStyledAttributes()方法获得自定义控件的主题样式数组
  2. 就是遍历每个属性来获得对应属性的值,也就是我们在xml布局文件中写的属性值
  3. 就是在循环结束之后记得调用array.recycle()来回收资源
public XiaoMiStep(Context context) {
        this(context, null);
    }

public XiaoMiStep(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
public XiaoMiStep(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获得atts.xml定义的属性值,存储在TypedArray中
        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.XiaoMiStep, defStyleAttr, 0);
        int n = ta.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = ta.getIndex(i);
            switch (attr) {
                case R.styleable.XiaoMiStep_backGroundColor: //背景颜色
                    background_color = ta.getColor(attr, Color.WHITE); //默认为白色
                    break;
                case R.styleable.XiaoMiStep_outerCircleColor: //最外侧圆
                    outer_circle_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_outerDotColor: //最外侧圆上的小圆点
                    outer_dot_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_lineColor:  //最外侧线的颜色
                    line_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_ringColor: //圆环的颜色
                    ring_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_stepNumColor: //步数的颜色
                    step_num_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_othetTextColor: //其他文字颜色
                    othet_text_color = ta.getColor(attr, Color.WHITE);
                    break;
            }
        }
        ta.recycle();
        init();
    }
初始化画笔
 private void init() {
        mPaint = new Paint(); //画笔
        mPaint.setAntiAlias(true);
        arcPaint = new Paint();  //圆环画笔
        arcPaint.setAntiAlias(true);
        textPaint = new Paint();  //文字画笔
        textPaint.setAntiAlias(true);
        pointPaint = new Paint(); //点
        pointPaint.setAntiAlias(true);
        animSet = new AnimatorSet(); //动画组合
    }
重写onMesure方法确定view大小

当你没有重写onMeasure方法时候,系统调用默认的onMeasure方法:
@OverrideprotectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
这个方法的作用是:测量控件的大小。其实Android系统在加载布局的时候是由系统测量各子View的大小来告诉父View我需要占多大空间,然后父View会根据自己的大小来决定分配多大空间给子View。MeasureSpec的specMode模式一共有三种:
MeasureSpec.EXACTLY:父视图希望子视图的大小是specSize中指定的大小;一般是设置了明确的值或者是MATCH_PARENT
MeasureSpec.AT_MOST:子视图的大小最多是specSize中的大小;表示子布局限制在一个最大值内,一般为WARP_CONTENT
MeasureSpec.UNSPECIFIED:父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;表示子布局想要多大就多大,很少使用。
想要设置WARP_CONTENT,只要重写onMeasure方法
另外,在onMeasure()方法里实现了动画效果。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);  //宽度的测量模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);  //宽度的测量值
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);  //高度的测量模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec); //高度的测量值
        //如果布局里面设置的是固定值,这里取布局里面的固定值;如果设置的是match_parent,则取父布局的大小
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            //如果布局里面没有设置固定值,这里取布局的宽度的1/2
            width = widthSize * 1 / 2;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            //如果布局里面没有设置固定值,这里取布局的高度的3/4
            height = heightSize * 3 / 4;
        }
        widthBg = width;
        heightBg = height;
        ra_out_circle = heightBg * 3 / 9;
        ra_inner_circle = heightBg * 3 / 10;
        line_length = 30;
        setMeasuredDimension(width, height);
        startAnim();
    }
重写onDraw方法进行绘画

代码已经很详细了。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制底层背景
        mPaint.setColor(background_color);
        mPaint.setStyle(Paint.Style.FILL);
        RectF rectF_back = new RectF(0, 0, widthBg, heightBg);
        canvas.drawRect(rectF_back, mPaint);
        //绘制最外层的圆
        mPaint.setColor(outer_circle_color);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(3);
        canvas.drawCircle(widthBg / 2, heightBg / 2, ra_out_circle, mPaint);
        //绘制圆上的小圆点
        pointPaint.setColor(outer_dot_color);
        pointPaint.setStrokeWidth(10);
        canvas.drawCircle((float) (widthBg / 2 + ra_out_circle * Math.cos(angle * 3.14 / 180)), (float) (heightBg / 2 + ra_out_circle * Math.sin(angle * 3.14 / 180)), 10, pointPaint);
        //画line
        drawLines(canvas);
        //画圆弧
        arcPaint.setStyle(Paint.Style.STROKE);
        arcPaint.setStrokeWidth(30);
        arcPaint.setColor(ring_color);
        RectF arcRect = new RectF((widthBg / 2 - ra_inner_circle + line_length / 2), (heightBg / 2 - ra_inner_circle + line_length / 2), (widthBg / 2 + ra_inner_circle - line_length / 2), (heightBg / 2 + ra_inner_circle - line_length / 2));
        canvas.drawArc(arcRect, -90, currentFootNumPre, false, arcPaint);

        //绘制步数
        textPaint.setColor(step_num_color);
        textPaint.setStrokeWidth(25);
        textPaint.setTextSize(widthBg / 6);
        canvas.drawText(String.valueOf(currentFootNum), (widthBg / 3 - 50), heightBg / 2 + 50, textPaint);
        textPaint.setStrokeWidth(10);
        textPaint.setColor(othet_text_color);
        textPaint.setTextSize(widthBg / 20);
        canvas.drawText("步", (widthBg / 2 + 200), heightBg / 2 + 50, textPaint);
        //绘制公里
        currentDistance = (float) (myFootNum * 6.4 / 8000);
        //小数点后一位
        java.text.DecimalFormat df = new java.text.DecimalFormat("#.0");
        String currentDis = df.format(currentDistance);
        canvas.drawText(currentDis + "公里", (widthBg / 3 - 30), heightBg / 2 + 150, textPaint);
        //中间竖线
        mPaint.setStrokeWidth(8);
        canvas.drawLine(widthBg / 2 + 10, heightBg / 2 + 110, widthBg / 2 + 10, heightBg / 2 + 155, mPaint);
        //绘制卡路里
        currentCal = myFootNum * 230 / 8000;
        canvas.drawText(String.valueOf(currentCal) + "千卡", (widthBg / 2 + 40), heightBg / 2 + 150, textPaint);


    }

    private void drawLines(Canvas canvas) {
        mPaint.setColor(line_color);
        mPaint.setStrokeWidth(4);
        for (int i = 0; i < 360; i++) {
            canvas.drawLine(widthBg / 2, (heightBg / 2 - ra_inner_circle), widthBg / 2, (heightBg / 2 - ra_inner_circle + line_length), mPaint);
            canvas.rotate(1, widthBg / 2, heightBg / 2);
        }
    }

默认一圈代表8000步,6.4公里,230千卡,初始步数根据以下代码设置。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xiao_mi_setp);
        ButterKnife.bind(this);

        mXiaoMiStep.setMyFootNum(4500);
    }
  public void setMyFootNum(int myFootNum) {
        this.myFootNum = myFootNum;
    }
实现动画

主要是
外圆上的小圆点动画,是根据角度确定。
步数动画在 0-myFootNum之间
画圆弧的动画在 0-myFootNum * 360 / 8000

  private void startAnim() {
        //小圆点动画
        final ValueAnimator dotAnimator =ValueAnimator.ofInt(-90, (myFootNum*360/8000-90));

        dotAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                angle = (int) dotAnimator.getAnimatedValue();
                postInvalidate();
            }
        });
        dotAnimator.setInterpolator(new LinearInterpolator());



        //步数动画实现
        final ValueAnimator walkAnimator = ValueAnimator.ofInt(0, myFootNum);
        walkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentFootNum = (int) walkAnimator.getAnimatedValue();
                postInvalidate();
            }
        });


        //画弧动画的实现
        final ValueAnimator arcAnimator = ValueAnimator.ofInt(0, (myFootNum * 360 / 8000));
        arcAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentFootNumPre = (int) arcAnimator.getAnimatedValue();
                postInvalidate();
            }
        });
        animSet.setDuration(3000);
        animSet.playTogether(walkAnimator, arcAnimator, dotAnimator);
        animSet.start();
    }

效果图如下所示,当然,你也可以通过改变xml布局的custom选项,来改变各个部分的颜色。

自定义View(三),仿小米运动计步_第2张图片

代码下载 https://github.com/baojie0327/ViewAndGroup

你可能感兴趣的:(自定义View(三),仿小米运动计步)