自定義View(三)伟葫,仿小米運(yùn)動計步

前面主要說了自定義View的一些知識恨搓,這篇文章主要是利用自定義View做一個仿小米運(yùn)動計步功能的控件,如下圖所示:



分析一下思路:
1.畫背景
2.畫一個最外部的圓
3.畫圓上的小圓點
4.畫豎線筏养,環(huán)繞一周
5.畫圓環(huán)
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>
        <!--圓環(huán)的顏色-->
        <attr name="ringColor" format="color"></attr>
        <!--步數(shù)顏色-->
        <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一般需要實現(xiàn)一下三個構(gòu)造方法慕蔚,這三個構(gòu)造方法是一層調(diào)用一層的丐黄,屬于遞進(jìn)關(guān)系。因此孔飒,我們只需要在最后一個構(gòu)造方法中來獲得View的屬性了灌闺。

  1. 通過theme.obtainStyledAttributes()方法獲得自定義控件的主題樣式數(shù)組
  2. 就是遍歷每個屬性來獲得對應(yīng)屬性的值艰争,也就是我們在xml布局文件中寫的屬性值
  3. 就是在循環(huán)結(jié)束之后記得調(diào)用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); //默認(rèn)為白色
                    break;
                case R.styleable.XiaoMiStep_outerCircleColor: //最外側(cè)圓
                    outer_circle_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_outerDotColor: //最外側(cè)圓上的小圓點
                    outer_dot_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_lineColor:  //最外側(cè)線的顏色
                    line_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_ringColor: //圓環(huán)的顏色
                    ring_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.XiaoMiStep_stepNumColor: //步數(shù)的顏色
                    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();  //圓環(huán)畫筆
        arcPaint.setAntiAlias(true);
        textPaint = new Paint();  //文字畫筆
        textPaint.setAntiAlias(true);
        pointPaint = new Paint(); //點
        pointPaint.setAntiAlias(true);
        animSet = new AnimatorSet(); //動畫組合
    }
重寫onMesure方法確定view大小

當(dāng)你沒有重寫onMeasure方法時候桂对,系統(tǒng)調(diào)用默認(rèn)的onMeasure方法:
@OverrideprotectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
這個方法的作用是:測量控件的大小甩卓。其實Android系統(tǒng)在加載布局的時候是由系統(tǒng)測量各子View的大小來告訴父View我需要占多大空間,然后父View會根據(jù)自己的大小來決定分配多大空間給子View蕉斜。MeasureSpec的specMode模式一共有三種:
MeasureSpec.EXACTLY:父視圖希望子視圖的大小是specSize中指定的大杏馐痢;一般是設(shè)置了明確的值或者是MATCH_PARENT
MeasureSpec.AT_MOST:子視圖的大小最多是specSize中的大姓恕机错;表示子布局限制在一個最大值內(nèi),一般為WARP_CONTENT
MeasureSpec.UNSPECIFIED:父視圖不對子視圖施加任何限制父腕,子視圖可以得到任意想要的大腥醴恕;表示子布局想要多大就多大璧亮,很少使用萧诫。
想要設(shè)置WARP_CONTENT,只要重寫onMeasure方法
另外枝嘶,在onMeasure()方法里實現(xiàn)了動畫效果财搁。

 @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); //高度的測量值
        //如果布局里面設(shè)置的是固定值,這里取布局里面的固定值;如果設(shè)置的是match_parent,則取父布局的大小
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            //如果布局里面沒有設(shè)置固定值,這里取布局的寬度的1/2
            width = widthSize * 1 / 2;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            //如果布局里面沒有設(shè)置固定值,這里取布局的高度的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方法進(jìn)行繪畫

代碼已經(jīng)很詳細(xì)了。

 @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);

        //繪制步數(shù)
        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);
        //小數(shù)點后一位
        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);
        }
    }

默認(rèn)一圈代表8000步躬络,6.4公里,230千卡搭儒,初始步數(shù)根據(jù)以下代碼設(shè)置穷当。

@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;
    }
實現(xiàn)動畫

主要是
外圓上的小圓點動畫,是根據(jù)角度確定淹禾。
步數(shù)動畫在 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());



        //步數(shù)動畫實現(xiàn)
        final ValueAnimator walkAnimator = ValueAnimator.ofInt(0, myFootNum);
        walkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentFootNum = (int) walkAnimator.getAnimatedValue();
                postInvalidate();
            }
        });


        //畫弧動畫的實現(xiàn)
        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();
    }

效果圖如下所示馁菜,當(dāng)然,你也可以通過改變xml布局的custom選項铃岔,來改變各個部分的顏色汪疮。

代碼下載 https://github.com/baojie0327/ViewAndGroup

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市毁习,隨后出現(xiàn)的幾起案子智嚷,更是在濱河造成了極大的恐慌,老刑警劉巖纺且,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盏道,死亡現(xiàn)場離奇詭異,居然都是意外死亡载碌,警方通過查閱死者的電腦和手機(jī)猜嘱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門衅枫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朗伶,你說我怎么就攤上這事弦撩。” “怎么了论皆?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵益楼,是天一觀的道長。 經(jīng)常有香客問我纯丸,道長偏形,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任觉鼻,我火速辦了婚禮俊扭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坠陈。我一直安慰自己萨惑,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布仇矾。 她就那樣靜靜地躺著庸蔼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贮匕。 梳的紋絲不亂的頭發(fā)上姐仅,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音刻盐,去河邊找鬼掏膏。 笑死,一個胖子當(dāng)著我的面吹牛敦锌,可吹牛的內(nèi)容都是我干的馒疹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼乙墙,長吁一口氣:“原來是場噩夢啊……” “哼颖变!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起听想,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤腥刹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后汉买,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肛走,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了朽色。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邻吞。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖葫男,靈堂內(nèi)的尸體忽然破棺而出抱冷,到底是詐尸還是另有隱情,我是刑警寧澤梢褐,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布旺遮,位于F島的核電站,受9級特大地震影響盈咳,放射性物質(zhì)發(fā)生泄漏耿眉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一鱼响、第九天 我趴在偏房一處隱蔽的房頂上張望鸣剪。 院中可真熱鬧,春花似錦丈积、人聲如沸筐骇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铛纬。三九已至,卻和暖如春唬滑,著一層夾襖步出監(jiān)牢的瞬間告唆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工晶密, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留悔详,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓惹挟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缝驳。 傳聞我的和親對象是個殘疾皇子连锯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,860評論 25 707
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 3,846評論 1 22
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件用狱、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評論 4 62
  • 月亮大了好多 亮亮的 泛著熒光 偽裝在路燈中 臺階上 落滿了漸黃的樹葉 在泛黃的路燈光中輪廓分明 秋天的景象 斑駁...
    韓超的小倉庫閱讀 229評論 0 0
  • 文:滕小七 一個人微信有沒有秒回你,就知道他喜歡不喜歡你溺忧?他秒回你咏连,不過是他在線盯孙,卻沒有主動聯(lián)系你。因為如果我喜歡...
    滕小七閱讀 720評論 0 1