Android進階之自定義View(2)高仿釘釘運動步數(shù)實現(xiàn)可動的進度圓環(huán)(上)

本文比較詳細的介紹了繪制圓環(huán)及圓弧的基礎(chǔ)知識,為實現(xiàn)釘釘運動步數(shù)打下基礎(chǔ),實現(xiàn)了下面的效果,實現(xiàn)釘釘運動就灰常簡單了,本文實現(xiàn)的初步效果如下:

如果想直接看釘釘運動的最終效果,請戳:Android進階之自定義控件(2)高仿釘釘運動步數(shù)實現(xiàn)可動的進度圓環(huán)(下)

Animation.gif

1、圓環(huán)的繪制
2、繪制背景圓環(huán)和進度圓環(huán)
3、繪制中間的文字
(1)使用drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)繪制圓環(huán):
image.png

 public class SportStepView extends View {
    private Paint mPaint;
    //圓環(huán)繪制的寬度
    private int mRoundWidth = 40;

    public SportStepView(Context context) {
        this(context, null);
    }

    public SportStepView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取寬的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //獲取寬的尺寸
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        
        //對wrap_content這種模式進行處理
        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = widthSize;
        } 
        //繪制圓環(huán)以寬度為標準,保存丈量結(jié)果
        setMeasuredDimension(widthSize, heightSize);
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(mRoundWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
      //繪制圓,設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
      //canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
        RectF oval = new RectF(0 , 0, getWidth(), getWidth());
        //畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
        canvas.drawArc(oval, 0, 360, false, mPaint);
    }
}

image.png

會發(fā)現(xiàn)顯示不全,繪制超出邊界了,因為圓的寬度是在當(dāng)前半徑向兩邊展開的。如下圖分析得知,圓所在的矩形區(qū)域不是rect(為屏幕的矩形區(qū)域),而是real Rect所在的區(qū)域:


image.png

因此只需要修改如下即可。

 RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
// RectF oval = new RectF(0 , 0, getWidth(), getWidth());
//畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
 canvas.drawArc(oval, 0, 360, false, mPaint);

修改后達到我們的預(yù)期效果:


image.png

(2)如果只是繪制上面的圓環(huán)效果,還可以使用: canvas.drawCircle()的方式實現(xiàn),這種方法更簡單:

 //繪制圓,設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
 //由于圓環(huán)本身有寬度,所以半徑要減去圓環(huán)寬度的一半,不然一部分圓會在view外面。
 canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);

(3)接下來我們來實現(xiàn)背景圓環(huán)+進度圓環(huán)的效果了,利用drawCircle繪制背景圓環(huán),drawArc()繪制進度圓環(huán),預(yù)期效果如下:


image.png

這個很簡單,再畫個圓弧即可:

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制背景圓環(huán),設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
        //正常情況下
       canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);

       //正常情況下,繪制進度圓環(huán)
      RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
            //畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
            canvas.drawArc(oval, 0, 300, false, mProgressPaint);
    }

但是光這樣處理,會有個小問題,就是當(dāng)背景圓環(huán)和進度圓環(huán)寬度不一致時,會出現(xiàn)下面的問題。


image.png

解決方法:以寬度較大為準即可。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        /**
         * 如果背景圓環(huán)和進度圓環(huán)寬度不一致,都以較大的寬度為準繪制。避免出現(xiàn)兩者顯示不居中的問題
         */
        //繪制背景圓環(huán),設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
//        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
        if (mRoundWidth < mProgressRoundWidth) {
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mProgressRoundWidth / 2, mPaint);
        } else {
            //正常情況下
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
        }

        if (mRoundWidth < mProgressRoundWidth) {
            // //正常情況下,繪制進度圓環(huán)
            RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
            //        RectF oval = new RectF(0 , 0, getWidth(), getWidth());
            //畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
            canvas.drawArc(oval, 0, 300, false, mProgressPaint);
        } else {
            //繪制進度圓環(huán)
            RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
            //畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
            canvas.drawArc(oval, 0, 300, false, mProgressPaint);
        }
    }

(4)繪制居中的進度文字


image.png

代碼實現(xiàn):


   //繪制中間的文字
        Rect textRect = new Rect();
        //進度百分比
        int progressPercent = (int) (mCurrentProgress * 1f / mMaxProgress * 100);
        String mShowText = progressPercent + "%";
        mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
        canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
image.png

(5)處理圓環(huán)進度和文字進度的動態(tài)顯示
方法一:開一個分線程,動態(tài)改變進度的值,不斷繪制達到進度變化的效果。
方法二:下篇會介紹到_
在測試的Activity中使用:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        mytextview = findViewById(R.id.mytextview);
        final SportStepView sportStepView = findViewById(R.id.sportstepview);
        mCurrentProgress = 80;
        //速度,值越大,變化速度越快
        rate = 1;
        //開一個分線程,動態(tài)改變進度的值,不斷繪制達到進度變化的效果
        new Thread(new Runnable() {
            @Override
            public void run() {
                sportStepView.setCurrentProgress(0);

                for (int i = 0; i < mCurrentProgress / rate; i++) {
                    sportStepView.setCurrentProgress(sportStepView.getCurrentProgress() + rate);
                    SystemClock.sleep(20);
//                pb_progress.invalidate();//invalidate()必須在主線程中執(zhí)行,此處不能使用
                    sportStepView.postInvalidate();//強制重繪,postInvalidate()可以在主線程也可以在分線程中執(zhí)行
                }
            }
        }).start();
    }

完整代碼:

package com.example.jojo.learn.customview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.example.jojo.learn.R;

/**
 * Created by JoJo on 2018/7/31.
 * wechat:18510829974
 * description: 仿釘釘運動步數(shù)
 */

public class SportStepView extends View {

    //繪制背景圓環(huán)的畫筆
    private Paint mPaint;
    //繪制外面進度的圓環(huán)的畫筆
    private Paint mProgressPaint;
    //繪制外面進度的圓環(huán)的畫筆
    private Paint mTextPaint;
    //背景圓弧的繪制的寬度
    private int mRoundWidth = 40;
    //進度圓環(huán)的寬度
    private float mProgressRoundWidth = 60;
    private int mTextSize = 40;//單位 sp

    //圓環(huán)最大進度
    private int mMaxProgress = 100;
    //圓環(huán)當(dāng)前進度
    private int mCurrentProgress = 0;

    public SportStepView(Context context) {
        this(context, null);
    }

    public SportStepView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取寬的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //獲取寬的尺寸
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //對wrap_content這種模式進行處理
        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = widthSize;
        }
        //以寬度為標準保存丈量結(jié)果
        setMeasuredDimension(widthSize, heightSize);
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(mRoundWidth);

        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true);// 抗鋸齒效果
        mProgressPaint.setStyle(Paint.Style.STROKE);
        mProgressPaint.setColor(Color.YELLOW);
        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);// 圓形筆頭
        mProgressPaint.setStrokeWidth(mProgressRoundWidth);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);// 抗鋸齒效果
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setColor(Color.BLACK);
        mTextPaint.setTextSize(sp2px(mTextSize));


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);


        /**
         * 如果背景圓環(huán)和進度圓環(huán)寬度不一致,都以較大的寬度為準繪制。避免出現(xiàn)兩者顯示不居中的問題
         */
        //繪制背景圓環(huán),設(shè)置畫筆的Style為Paint.Style.STROKE,則繪制出來的為圓環(huán),否則繪制出來的為圓
//        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
        if (mRoundWidth < mProgressRoundWidth) {
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mProgressRoundWidth / 2, mPaint);
        } else {
            //正常情況下
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mRoundWidth / 2, mPaint);
        }

        if (mRoundWidth < mProgressRoundWidth) {
            // //正常情況下,繪制進度圓環(huán)
            RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
            //        RectF oval = new RectF(0 , 0, getWidth(), getWidth());
            //畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
            canvas.drawArc(oval, 0 + 90, mCurrentProgress * 1f / mMaxProgress * 360, false, mProgressPaint);
        } else {
            //繪制進度圓環(huán)
            RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
            //畫圓弧 useCenter:是否顯示圓內(nèi)的橫線 下面的繪制0,360的圓弧,也可以實現(xiàn)繪制圓環(huán)的效果
            canvas.drawArc(oval, 0 + 90, mCurrentProgress * 1f / mMaxProgress * 360, false, mProgressPaint);
        }


        //繪制中間的文字
        Rect textRect = new Rect();
        //進度百分比
        int progressPercent = (int) (mCurrentProgress * 1f / mMaxProgress * 100);
        String mShowText = progressPercent + "%";
        mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
        canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
    }

    public void setCurrentProgress(int currentProgress) {
        this.mCurrentProgress = currentProgress;
    }

    public void setMaxProgress(int maxProgress) {
        this.mMaxProgress = maxProgress;
    }

    public int getMaxProgress() {
        return mMaxProgress;
    }

    public int getCurrentProgress() {
        return mCurrentProgress;
    }


    /**
     * 將sp轉(zhuǎn)換成px
     *
     * @param sp
     * @return
     */
    private int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
                getResources().getDisplayMetrics());
    }
}

涉及到的自定義屬性

   <!--SportStepView-->
    <declare-styleable name="SportStepView">
        <!--圓環(huán)半徑-->
        <attr name="radius" format="dimension"></attr>
        <attr name="outerRoundColor" format="color"></attr>
        <attr name="innerRoundColor" format="color"></attr>
    </declare-styleable>
最后,附上我的一個Kotlin編寫+組件化開發(fā)的開源項目Designer

Kotlin+組件化開發(fā)實踐—開源項目Designer-App

Designer項目算是傾注了我蠻多心血了,每個頁面和功能都當(dāng)成是上線的App來做,App的logo還特地做了UI設(shè)計??力求做到精致和完善,其中還包括了很多自己項目開發(fā)中的經(jīng)驗匯總和對新技術(shù)的探索和整合,希望對各位讀者有所幫助,歡迎點個star,follow,或者給個小心心,嘻嘻??也可以分享給你更多的朋友一起學(xué)習(xí),您的支持是我不斷前進的動力。如果有任何問題,歡迎在GitHub上給我提issue或者留言。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,996評論 2 374

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