自定義View(三),仿小米運(yùn)動(dòng)計(jì)步

前面主要說(shuō)了自定義View的一些知識(shí),這篇文章主要是利用自定義View做一個(gè)仿小米運(yùn)動(dòng)計(jì)步功能的控件,如下圖所示:



分析一下思路:
1.畫(huà)背景
2.畫(huà)一個(gè)最外部的圓
3.畫(huà)圓上的小圓點(diǎn)
4.畫(huà)豎線,環(huán)繞一周
5.畫(huà)圓環(huán)
6.畫(huà)文字
7.添加動(dòng)畫(huà)
為了可以自定義各個(gè)控件的顯示效果,自定義View的屬性還是必要的。

自定義屬性

自定義屬性主要是自定義了各個(gè)部件的顏色,format是該屬性的取值類型。
這里要注意的是,自定義屬性的name定義成了XiaoMiStep,那么自定義View的名字也要是XiaoMiStep,保持一致。

<declare-styleable name="XiaoMiStep">
        <!--背景-->
        <attr name="backGroundColor" format="color"></attr>
        <!--最外層圓-->
        <attr name="outerCircleColor" format="color"></attr>
        <!--外層圓上的小圓點(diǎn)顏色-->
        <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布局文件里引用的時(shí)候,代碼如下:

<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一般需要實(shí)現(xiàn)一下三個(gè)構(gòu)造方法,這三個(gè)構(gòu)造方法是一層調(diào)用一層的,屬于遞進(jìn)關(guān)系。因此,我們只需要在最后一個(gè)構(gòu)造方法中來(lái)獲得View的屬性了。

  1. 通過(guò)theme.obtainStyledAttributes()方法獲得自定義控件的主題樣式數(shù)組
  2. 就是遍歷每個(gè)屬性來(lái)獲得對(duì)應(yīng)屬性的值,也就是我們?cè)趚ml布局文件中寫(xiě)的屬性值
  3. 就是在循環(huán)結(jié)束之后記得調(diào)用array.recycle()來(lái)回收資源
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定義的屬性值,存儲(chǔ)在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è)圓上的小圓點(diǎn)
                    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();
    }
初始化畫(huà)筆
 private void init() {
        mPaint = new Paint(); //畫(huà)筆
        mPaint.setAntiAlias(true);
        arcPaint = new Paint();  //圓環(huán)畫(huà)筆
        arcPaint.setAntiAlias(true);
        textPaint = new Paint();  //文字畫(huà)筆
        textPaint.setAntiAlias(true);
        pointPaint = new Paint(); //點(diǎn)
        pointPaint.setAntiAlias(true);
        animSet = new AnimatorSet(); //動(dòng)畫(huà)組合
    }
重寫(xiě)onMesure方法確定view大小

當(dāng)你沒(méi)有重寫(xiě)onMeasure方法時(shí)候,系統(tǒng)調(diào)用默認(rèn)的onMeasure方法:
@OverrideprotectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
這個(gè)方法的作用是:測(cè)量控件的大小。其實(shí)Android系統(tǒng)在加載布局的時(shí)候是由系統(tǒng)測(cè)量各子View的大小來(lái)告訴父View我需要占多大空間,然后父View會(huì)根據(jù)自己的大小來(lái)決定分配多大空間給子View。MeasureSpec的specMode模式一共有三種:
MeasureSpec.EXACTLY:父視圖希望子視圖的大小是specSize中指定的大??;一般是設(shè)置了明確的值或者是MATCH_PARENT
MeasureSpec.AT_MOST:子視圖的大小最多是specSize中的大?。槐硎咀硬季窒拗圃谝粋€(gè)最大值內(nèi),一般為WARP_CONTENT
MeasureSpec.UNSPECIFIED:父視圖不對(duì)子視圖施加任何限制,子視圖可以得到任意想要的大小;表示子布局想要多大就多大,很少使用。
想要設(shè)置WARP_CONTENT,只要重寫(xiě)onMeasure方法
另外,在onMeasure()方法里實(shí)現(xiàn)了動(dòng)畫(huà)效果。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);  //寬度的測(cè)量模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);  //寬度的測(cè)量值
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);  //高度的測(cè)量模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec); //高度的測(cè)量值
        //如果布局里面設(shè)置的是固定值,這里取布局里面的固定值;如果設(shè)置的是match_parent,則取父布局的大小
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            //如果布局里面沒(méi)有設(shè)置固定值,這里取布局的寬度的1/2
            width = widthSize * 1 / 2;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            //如果布局里面沒(méi)有設(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();
    }
重寫(xiě)onDraw方法進(jìn)行繪畫(huà)

代碼已經(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);
        //繪制圓上的小圓點(diǎn)
        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);
        //畫(huà)line
        drawLines(canvas);
        //畫(huà)圓弧
        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ù)點(diǎn)后一位
        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;
    }
實(shí)現(xiàn)動(dòng)畫(huà)

主要是
外圓上的小圓點(diǎn)動(dòng)畫(huà),是根據(jù)角度確定。
步數(shù)動(dòng)畫(huà)在 0-myFootNum之間
畫(huà)圓弧的動(dòng)畫(huà)在 0-myFootNum * 360 / 8000

  private void startAnim() {
        //小圓點(diǎn)動(dòng)畫(huà)
        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ù)動(dòng)畫(huà)實(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();
            }
        });


        //畫(huà)弧動(dòng)畫(huà)的實(shí)現(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)然,你也可以通過(guò)改變xml布局的custom選項(xiàng),來(lái)改變各個(gè)部分的顏色。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,189評(píng)論 25 708
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 4,076評(píng)論 1 22
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,429評(píng)論 4 61
  • 月亮大了好多 亮亮的 泛著熒光 偽裝在路燈中 臺(tái)階上 落滿了漸黃的樹(shù)葉 在泛黃的路燈光中輪廓分明 秋天的景象 斑駁...
    韓超的小倉(cāng)庫(kù)閱讀 266評(píng)論 0 0
  • 文:滕小七 一個(gè)人微信有沒(méi)有秒回你,就知道他喜歡不喜歡你?他秒回你,不過(guò)是他在線,卻沒(méi)有主動(dòng)聯(lián)系你。因?yàn)槿绻蚁矚g...
    滕小七閱讀 792評(píng)論 0 1

友情鏈接更多精彩內(nèi)容