Android自定義View實現(xiàn)圓弧進度效果

前言:Android開發(fā)中勇婴,自定義View實現(xiàn)自己想要的效果已成為一項必備的技能鹦赎,當然自定義View也是Android開發(fā)中比較難的部分,涉及到的知識有Canvas(畫布),Paint(畫筆)等距淫,自定義控件分為三種:一是直接繼承自View申窘,完全的自定義;二是在原有控件的基礎上進行改造馋劈,達到自己想要的效果攻锰;還有一種就是自定義組合控件晾嘶,將已有的控件根據(jù)自己的需要進行組合實現(xiàn)的效果。本人對自定義View也是一知半解娶吞,簡單記錄下自己學習自定義View(繼承自View)的過程垒迂,方便日后翻閱。

技術實現(xiàn)

1.ArcView繼承自View

2.Canvas(畫布)

3.Paint(畫筆)

效果圖:類似于QQ的計步效果

效果圖

1.繼承自View

(1)重寫3個構(gòu)造方法(新的API中的構(gòu)造方法是4個)

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

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

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

(2)重寫View的OnDraw方法

@SuppressLint("DrawAllocation")
@Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    centerX=getWidth()/2;
    centerY=getHeight()/2;
    //初始化paint
    initPaint();
    //繪制弧度
    drawArc(canvas);
    //繪制文本
    drawText(canvas); 
}

注:這里的paint初始化我放在了onDraw方法中進行的妒蛇,當然你也可以放在有三個參數(shù)的構(gòu)造方法中初始化机断。

2.Paint初始化

(1)圓弧的畫筆mArcPaint

//圓弧的paint 
mArcPaint=new Paint(Paint.*ANTI_ALIAS_FLAG*); 
//抗鋸齒 
mArcPaint.setAntiAlias(true); 
mArcPaint.setColor(Color.parseColor("#666666")); 
//設置透明度(數(shù)值為0-255) 
mArcPaint.setAlpha(100); 
//設置畫筆的畫出的形狀 
mArcPaint.setStrokeJoin(Paint.Join.*ROUND*); 
mArcPaint.setStrokeCap(Paint.Cap.*ROUND*); 
//設置畫筆類型 
mArcPaint.setStyle(Paint.Style.*STROKE*); 
mArcPaint.setStrokeWidth(dp2px(mStrokeWith));

(2)文字的畫筆mTextPaint

//中心文字的paint 
mTextPaint=new Paint(); 
mTextPaint.setAntiAlias(true); 
mTextPaint.setColor(Color.parseColor("#FF4A40")); 
//設置文本的對齊方式 
mTextPaint.setTextAlign(Paint.Align.*CENTER*); 
//mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12)); 
mTextPaint.setTextSize(dp2px(25));

3.Canvas繪制

(1)圓弧的繪制

/**
*繪制圓弧
*@param canvas
*/
 private void drawArc(Canvas canvas) {
    //繪制圓弧背景
   RectF mRectF=new RectF(mStrokeWith+dp2px(5),mStrokeWith+dp2px(5),getWidth()-mStrokeWith-dp2px(5),getHeight()-mStrokeWith);
   canvas.drawArc(mRectF,startAngle,mAngle,false,mArcPaint);
   //繪制當前數(shù)值對應的圓弧
   mArcPaint.setColor(Color.parseColor("#FF4A40"));
   //根據(jù)當前數(shù)據(jù)繪制對應的圓弧
   canvas.drawArc(mRectF,startAngle,mIncludedAngle,false,mArcPaint); 
}

(2)文本的繪制

/**
* 繪制文本
* @param canvas
*/
private void drawText(Canvas canvas) {
    Rect mRect=new Rect();
   String mValue=String.valueOf(mAnimatorValue);
   //繪制中心的數(shù)值
   mTextPaint.getTextBounds(mValue,0,mValue.length(),mRect);
   canvas.drawText(String.valueOf(mAnimatorValue),centerX,centerY+mRect.height(),mTextPaint);    
   //繪制中心文字描述
   mTextPaint.setColor(Color.parseColor("#999999"));
   mTextPaint.setTextSize(dp2px(12));
   mTextPaint.getTextBounds(mDes,0,mDes.length(),mRect);
   canvas.drawText(mDes,centerX,centerY+2*mRect.height()+dp2px(10),mTextPaint);    //繪制最小值
   String minValue=String.valueOf(mMinValue);
   String maxValue=String.valueOf(mMaxValue);
   mTextPaint.setTextSize(dp2px(18));
   mTextPaint.getTextBounds(minValue,0,minValue.length(),mRect);
   canvas.drawText(minValue, (float) (centerX-0.6*centerX-dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
   //繪制最大值
   mTextPaint.getTextBounds(maxValue,0,maxValue.length(),mRect);
   canvas.drawText(maxValue, (float) (centerX+0.6*centerX+dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint); 
}

4.添加動畫效果及數(shù)據(jù)

(1)動畫效果

/** 
*  為繪制弧度及數(shù)據(jù)設置動畫
*  @param startAngle 開始的弧度
*  @param currentAngle 需要繪制的弧度
*  @param currentValue 需要繪制的數(shù)據(jù)
*  @param time 動畫執(zhí)行的時長
*/
private void setAnimation(float startAngle, float currentAngle,int currentValue, int time) {
    //繪制當前數(shù)據(jù)對應的圓弧的動畫效果
    ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
    progressAnimator.setDuration(time);
    progressAnimator.setTarget(mIncludedAngle);
    progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mIncludedAngle = (float) animation.getAnimatedValue();
            //重新繪制,不然不會出現(xiàn)效果
            postInvalidate();
        }
    });
    //開始執(zhí)行動畫
    progressAnimator.start();    //中心數(shù)據(jù)的動畫效果
    ValueAnimator valueAnimator = ValueAnimator.ofInt(mAnimatorValue, currentValue);
    valueAnimator.setDuration(2500);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {

            mAnimatorValue = (int) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    valueAnimator.start(); 
}

(2)數(shù)據(jù)添加

/** 
* 設置數(shù)據(jù)
* @param minValue 最小值
* @param maxValue 最大值
* @param currentValue 當前繪制的值
* @param des 描述信息
*/
public void setValues(int minValue,int maxValue, int currentValue,String des) {
    mDes=des;
    mMaxValue=maxValue;
    mMinValue=minValue;
    //完全覆蓋背景弧度
    if (currentValue > maxValue) {
        currentValue = maxValue;
    }
    //計算弧度比重
    float scale = (float) currentValue / maxValue;
    //計算弧度
    float currentAngle = scale * mAngle;
    //開始執(zhí)行動畫
    setAnimation(0, currentAngle, currentValue,2500);
}

完整代碼:

/** 
*Created by ruancw on 2018/6/13.
*自定義的圓弧形view
*/
public class ArcView extends View {

    //根據(jù)數(shù)據(jù)顯示的圓弧Paint
    private Paint mArcPaint;
    //文字描述的paint
    private Paint mTextPaint;
    //圓弧開始的角度
    private float startAngle=135;
    //圓弧結(jié)束的角度
    private float endAngle=45;
    //圓弧背景的開始和結(jié)束間的夾角大小
    private float mAngle=270;
    //當前進度夾角大小
    private float mIncludedAngle=0;
    //圓弧的畫筆的寬度
    private float mStrokeWith=10;
    //中心的文字描述
    private String mDes="";
    //動畫效果的數(shù)據(jù)及最大/小值
    private int mAnimatorValue,mMinValue,mMaxValue;
    //中心點的XY坐標
    private float centerX,centerY;   
    public ArcView(Context context) {
        this(context,null);
    }

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

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

    private void initPaint() {
        //圓弧的paint
        mArcPaint=new Paint(Paint.*ANTI_ALIAS_FLAG*);
        //抗鋸齒
        mArcPaint.setAntiAlias(true);
        mArcPaint.setColor(Color.parseColor("#666666"));
        //設置透明度(數(shù)值為0-255)
        mArcPaint.setAlpha(100);
        //設置畫筆的畫出的形狀
        mArcPaint.setStrokeJoin(Paint.Join.*ROUND*);
        mArcPaint.setStrokeCap(Paint.Cap.*ROUND*);
        //設置畫筆類型
        mArcPaint.setStyle(Paint.Style.*STROKE*);
        mArcPaint.setStrokeWidth(dp2px(mStrokeWith));    
        //中心文字的paint
        mTextPaint=new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.parseColor("#FF4A40"));
        //設置文本的對齊方式
        mTextPaint.setTextAlign(Paint.Align.*CENTER*);
        //mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12));
        mTextPaint.setTextSize(dp2px(25));    
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        centerX=getWidth()/2;
        centerY=getHeight()/2;
        //初始化paint
        initPaint();
        //繪制弧度
        drawArc(canvas);
        //繪制文本
        drawText(canvas);
    }

    /**
    * 繪制文本
    * @param canvas
    */
    private void drawText(Canvas canvas) {
        Rect mRect=new Rect();
        String mValue=String.valueOf(mAnimatorValue);
        //繪制中心的數(shù)值
        mTextPaint.getTextBounds(mValue,0,mValue.length(),mRect);
        canvas.drawText(String.valueOf(mAnimatorValue),centerX,centerY+mRect.height(),mTextPaint);    
        //繪制中心文字描述
        mTextPaint.setColor(Color.parseColor("#999999"));
        mTextPaint.setTextSize(dp2px(12));
        mTextPaint.getTextBounds(mDes,0,mDes.length(),mRect);
        canvas.drawText(mDes,centerX,centerY+2*mRect.height()+dp2px(10),mTextPaint);    //繪制最小值
        String minValue=String.valueOf(mMinValue);
        String maxValue=String.valueOf(mMaxValue);
        mTextPaint.setTextSize(dp2px(18));
        mTextPaint.getTextBounds(minValue,0,minValue.length(),mRect);
        canvas.drawText(minValue, (float) (centerX-0.6*centerX-dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
        //繪制最大指
        mTextPaint.getTextBounds(maxValue,0,maxValue.length(),mRect);
        canvas.drawText(maxValue, (float) (centerX+0.6*centerX+dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
    }

    /**
    *繪制圓弧
    *@param canvas
    */
    private void drawArc(Canvas canvas) {
        //繪制圓弧背景
        RectF mRectF=new RectF(mStrokeWith+dp2px(5),mStrokeWith+dp2px(5),getWidth()-mStrokeWith-dp2px(5),getHeight()-mStrokeWith);
        canvas.drawArc(mRectF,startAngle,mAngle,false,mArcPaint);
        //繪制當前數(shù)值對應的圓弧
        mArcPaint.setColor(Color.parseColor("#FF4A40"));
        //根據(jù)當前數(shù)據(jù)繪制對應的圓弧
        canvas.drawArc(mRectF,startAngle,mIncludedAngle,false,mArcPaint);
    }

    /** 
    *  為繪制弧度及數(shù)據(jù)設置動畫
    *  @param startAngle 開始的弧度
    *  @param currentAngle 需要繪制的弧度
    *  @param currentValue 需要繪制的數(shù)據(jù)
    *  @param time 動畫執(zhí)行的時長
    */
    private void setAnimation(float startAngle, float currentAngle,int currentValue, int time) {
        //繪制當前數(shù)據(jù)對應的圓弧的動畫效果
        ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
        progressAnimator.setDuration(time);
        progressAnimator.setTarget(mIncludedAngle);
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mIncludedAngle = (float) animation.getAnimatedValue();
                //重新繪制绣夺,不然不會出現(xiàn)效果
                postInvalidate();
            }
        });
        //開始執(zhí)行動畫
        progressAnimator.start();    //中心數(shù)據(jù)的動畫效果
        ValueAnimator valueAnimator = ValueAnimator.ofInt(mAnimatorValue, currentValue);
        valueAnimator.setDuration(2500);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValue = (int) valueAnimator.getAnimatedValue();
                postInvalidate();
            }
        });
        valueAnimator.start();
    }

    /** 
    * 設置數(shù)據(jù)
    * @param minValue 最小值
    * @param maxValue 最大值
    * @param currentValue 當前繪制的值
    * @param des 描述信息
    */
    public void setValues(int minValue,int maxValue, int currentValue,String des) {
         mDes=des;
         mMaxValue=maxValue;
         mMinValue=minValue;
         //完全覆蓋背景弧度
         if (currentValue > maxValue) {
               currentValue = maxValue;
        }
        //計算弧度比重
        float scale = (float) currentValue / maxValue;
        //計算弧度
        float currentAngle = scale * mAngle;
        //開始執(zhí)行動畫
        setAnimation(0, currentAngle, currentValue,2500);
    }


    public float dp2px(float dp) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return dp * metrics.density;
    }
}

總結(jié):設置Paint的畫筆形狀(Cap和Join設置為弧形)吏奸;使用Canvas的drawArc方法繪制圓弧及drawText繪制文本信息等;ValueAnimator設置數(shù)據(jù)及當前圓弧進度的動畫效果陶耍。

歡迎評論與留言奋蔚,不足之處,歡迎指正!!你弦!

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市馒过,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酗钞,老刑警劉巖腹忽,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異砚作,居然都是意外死亡窘奏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門偎巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔼夜,“玉大人兼耀,你說我怎么就攤上這事压昼。” “怎么了瘤运?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵窍霞,是天一觀的道長。 經(jīng)常有香客問我拯坟,道長但金,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任郁季,我火速辦了婚禮冷溃,結(jié)果婚禮上钱磅,老公的妹妹穿的比我還像新娘。我一直安慰自己似枕,他們只是感情好盖淡,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凿歼,像睡著了一般褪迟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上答憔,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天味赃,我揣著相機與錄音,去河邊找鬼虐拓。 笑死心俗,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蓉驹。 我是一名探鬼主播另凌,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼戒幔!你這毒婦竟也來了吠谢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诗茎,失蹤者是張志新(化名)和其女友劉穎工坊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敢订,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡王污,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了楚午。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昭齐。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矾柜,靈堂內(nèi)的尸體忽然破棺而出阱驾,到底是詐尸還是另有隱情,我是刑警寧澤怪蔑,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布里覆,位于F島的核電站,受9級特大地震影響缆瓣,放射性物質(zhì)發(fā)生泄漏喧枷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隧甚。 院中可真熱鬧车荔,春花似錦、人聲如沸戚扳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咖城。三九已至茬腿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宜雀,已是汗流浹背切平。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辐董,地道東北人悴品。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像简烘,于是被迫代替她去往敵國和親苔严。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

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