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

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



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

自定义属性

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

<declare-styleable name="XiaoMiStep">
        <!--背景-->
        <attr name="backGroundColor" format="color"></attr>
        <!--最外层圆-->
        <attr name="outerCircleColor" format="color"></attr>
        <!--外层圆上的小圆点颜色-->
        <attr name="outerDotColor" format="color"></attr>
        <!--竖线的颜色-->
        <attr name="lineColor" format="color"></attr>
        <!--圆环的颜色-->
        <attr name="ringColor" format="color"></attr>
        <!--步数颜色-->
        <attr name="stepNumColor" format="color"></attr>
        <!--其他字的颜色-->
        <attr name="othetTextColor" format="color"></attr>
    </declare-styleable>

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

<com.example.ahuang.viewandgroup.view.XiaoMiStep
        android:id="@+id/xiaoMiStep"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:backGroundColor="#0FA9C1"
        custom:lineColor="#8ED8E5"
        custom:othetTextColor="#8ED8E5"
        custom:outerCircleColor="#8ED8E5"
        custom:outerDotColor="#ffffff"
        custom:ringColor="#8ED8E5"
        custom:stepNumColor="#ffffff"/>

记得最后要引入我们的命名空间
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选项,来改变各个部分的颜色。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,442评论 25 707
  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,817评论 1 22
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 月亮大了好多 亮亮的 泛着荧光 伪装在路灯中 台阶上 落满了渐黄的树叶 在泛黄的路灯光中轮廓分明 秋天的景象 斑驳...
    韩超的小仓库阅读 226评论 0 0
  • 文:滕小七 一个人微信有没有秒回你,就知道他喜欢不喜欢你?他秒回你,不过是他在线,却没有主动联系你。因为如果我喜欢...
    滕小七阅读 716评论 0 1