Android 自定義漸變圓弧

項目最近需要添加一個體質(zhì)監(jiān)測模塊,需要用到漸變的圓弧,先上效果圖

體質(zhì)監(jiān)測.png
體質(zhì)監(jiān)測.png

下面,講講我個人的繪制心得。

其實這個功能并不是很難,首先我們新建一個ProgressViewNew,在onMeasure()方法中設(shè)置控件的大小,代碼里的注解應(yīng)該很詳細

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
    }
/**
 *
 * @param origin
 * @param isWidth
 * @return
 */
private int measure(int origin, boolean isWidth){
    int result = DEFAULT_MIN_WIDTH;
    int specMode = MeasureSpec.getMode(origin);//得到模式
    int specSize = MeasureSpec.getSize(origin);//得到尺寸

    switch (specMode) {
        //EXACTLY是精確尺寸,當我們將控件的layout_width或layout_height指定為具體數(shù)值時如andorid:layout_width="50dip",或者為FILL_PARENT是,都是控件大小已經(jīng)確定的情況,都是精確尺寸。
        case MeasureSpec.EXACTLY:
            //AT_MOST是最大尺寸,當控件的layout_width或layout_height指定為WRAP_CONTENT時,控件大小一般隨著控件的子空間或內(nèi)容進行變化,此時控件尺寸只要不超過父控件允許的最大尺寸即可
        case MeasureSpec.AT_MOST:
            result = specSize;
            if (isWidth) {
//                    widthForUnspecified = result;
            } else {
//                    heightForUnspecified = result;
            }
            break;
        //UNSPECIFIED是未指定尺寸,這種情況不多,一般都是父控件是AdapterView,通過measure方法傳入的模式。
        case MeasureSpec.UNSPECIFIED:
        default:
            result = Math.min(result, specSize);
            if (isWidth) {//寬或高未指定的情況下,可以由另一端推算出來 - -如果兩邊都沒指定就用默認值
//                    result = (int) (heightForUnspecified * BODY_WIDTH_HEIGHT_SCALE);
            } else {
//                    result = (int) (widthForUnspecified / BODY_WIDTH_HEIGHT_SCALE);
            }
            if (result == 0) {
                result = DEFAULT_MIN_WIDTH;
            }
            break;
    }

    return result;
}

下面就是主要的核心代碼了,onDraw()方法中進行繪制

1. 繪制默認灰色的圓弧
    //1------繪制默認灰色的圓弧
    float r = Math.min(getHeight() / 2, getWidth() / 2);//半徑
    Paint paintDefalt = new Paint();
    paintDefalt.setColor(getResources().getColor(R.color.colordefault));
    //設(shè)置筆刷的樣式 Paint.Cap.Round ,Cap.SQUARE等分別為圓形、方形
    paintDefalt.setStrokeCap(Paint.Cap.SQUARE);
    float borderWidth = DEFAULT_BORDER_WIDTH;//圓弧寬度

    float centerX=(getHeight()-2*marging) / 2;
    //        RectF oval1=new RectF(marging,marging,getWidth()-marging,getHeight()-marging);
      RectF oval1=new RectF(marging,marging,2*centerX+marging,2*centerX+marging);
    /**
     * Paint.Style.FILL    :填充內(nèi)部
     Paint.Style.FILL_AND_STROKE  :填充內(nèi)部和描邊
     Paint.Style.STROKE  :僅描邊
     */
    paintDefalt.setStyle(Paint.Style.STROKE);//設(shè)置填充樣式
    paintDefalt.setAntiAlias(true);//抗鋸齒功能
    paintDefalt.setStrokeWidth(borderWidth);//設(shè)置畫筆寬度
    //        該類是第二個參數(shù)是角度的開始,第三個參數(shù)是多少度
    //        canvas.drawCircle(getWidth() / 2, getHeight() / 2, r, paintCircle);//畫圓,圓心在中心位置,半徑為長寬小者的一半

    /**
     * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//畫弧,
     參數(shù)一是RectF對象,一個矩形區(qū)域橢圓形的界限用于定義在形狀、大小、電弧,
     參數(shù)二是起始角(度)在電弧的開始,
     參數(shù)三掃描角(度)開始順時針測量的,參數(shù)四是如果這是真的話,包括橢圓中心的電弧,并關(guān)閉它,如果它是假這將是一個弧線,參數(shù)五是Paint對象;
     */
    //繪制默認灰色的圓弧
    canvas.drawArc(oval1, 180, 180, false, paintDefalt);//小弧形
    //        參數(shù)一為漸變起初點坐標x位置,參數(shù)二為y軸位置,參數(shù)三和四分辨對應(yīng)漸變終點,最后參數(shù)為平鋪方式,這里設(shè)置為鏡像.
2. 繪制當前進度的圓弧
    //2-----繪制當前進度的圓弧
    Paint paintCurrent = new Paint();
    paintCurrent.setStyle(Paint.Style.STROKE);//設(shè)置填充樣式
    paintCurrent.setAntiAlias(true);//抗鋸齒功能
    paintCurrent.setStrokeWidth(borderWidth);//設(shè)置畫筆寬度
    //設(shè)置筆刷的樣式 Paint.Cap.Round ,Cap.SQUARE等分別為圓形、方形
    paintCurrent.setStrokeCap(Paint.Cap.SQUARE);
    /**
     * static final Shader.TileMode CLAMP: 邊緣拉伸.
     static final Shader.TileMode MIRROR:在水平方向和垂直方向交替景象, 兩個相鄰圖像間沒有縫隙.
     Static final Shader.TillMode REPETA:在水平方向和垂直方向重復(fù)擺放,兩個相鄰圖像間有縫隙縫隙.
     */

    LinearGradient lg=new LinearGradient(0,0,100,100,colors,positions, Shader.TileMode.MIRROR);  //漸變顏色
    // 創(chuàng)建SweepGradient對象
    // 第一個,第二個參數(shù)中心坐標
    // 后面的參數(shù)與線性渲染相同

    //18.5/24.0/28.0/35.0
    positions[0]=Float.parseFloat(df.format(position_line[0]/maxCount));
    positions[1]=Float.parseFloat(df.format(position_line[1]/maxCount));
    positions[2]=Float.parseFloat(df.format(position_line[2]/maxCount));
    positions[3]=Float.parseFloat(df.format(position_line[3]/maxCount));
    positions[4]=Float.parseFloat(df.format(position_line[4]/maxCount));

    SweepGradient sweepGradient = new SweepGradient(centerX+padding, centerX+padding, colors, positions);
    Matrix matrix = new Matrix();
    matrix.setRotate(130, centerX, centerX);//加上旋轉(zhuǎn)還是很有必要的,每次最右邊總是有一部分多余了,不太美觀,也可以不加
    sweepGradient.setLocalMatrix(matrix);
    paintCurrent.setShader(sweepGradient);

    //        canvas.drawArc(oval1, 180, currentAngle, false, paintCurrent);//小弧形
    //當前進度
    canvas.drawArc(oval1, startAngle, currentAngle, false, paintCurrent);

注意:應(yīng)為圓弧是根據(jù)不同的數(shù)值而變化的,所以里面加入了,每個color顯示對應(yīng)的區(qū)域點

 //18.5/24.0/28.0/35.0
    positions[0]=Float.parseFloat(df.format(position_line[0]/maxCount));
    positions[1]=Float.parseFloat(df.format(position_line[1]/maxCount));
    positions[2]=Float.parseFloat(df.format(position_line[2]/maxCount));
    positions[3]=Float.parseFloat(df.format(position_line[3]/maxCount));
    positions[4]=Float.parseFloat(df.format(position_line[4]/maxCount));
4.繪制相關(guān)文字
    //3----文字
    //內(nèi)容顯示文字
    Paint vTextPaint = new Paint();
    vTextPaint.setTextSize(textSize);
    vTextPaint.setColor(Color.BLACK);
    vTextPaint.setAntiAlias(true);//抗鋸齒功能
//        vTextPaint.setStrokeWidth((float) 3.0);
    vTextPaint.setTextAlign(Paint.Align.CENTER);

    //TODO 等待修改
    canvas.drawText("瘦", marging, centerX+marging+textSize+padding, vTextPaint);
    canvas.drawText("胖", getWidth()-marging, centerX+marging+textSize+padding, vTextPaint);
    //圓環(huán)中心的文字
    vTextPaint.setTextSize(BIM_textSize);
    canvas.drawText("BMI指數(shù)", getWidth()/2, (int)((centerX+marging)/3*1.5+textSize), vTextPaint);

    //設(shè)置肥胖指數(shù)
    /**
     * 常用的字體風(fēng)格名稱還有:
     * Typeface.BOLD //粗體
     * Typeface.BOLD_ITALIC //粗斜體
     * Typeface.ITALIC //斜體
     * Typeface.NORMAL //常規(guī)
     */
    vTextPaint.setTextSize(Number_textSize);
    Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
    vTextPaint.setTypeface( font );

    canvas.drawText(PANG_NUMBER, getWidth()/2, (int)((centerX+marging)/3*2.5+textSize), vTextPaint);
5.最后在繪制圓上的小點,就完成了啊

這里繪制園上的點有兩種方法:

  1. 也就是本文中用到的,把畫布旋轉(zhuǎn)相應(yīng)的角度,繪制點,然后,在旋轉(zhuǎn)到原來的位置,就可以了,
    為什么要這么做呢,因為我們繪制的是一個圓,之前也想通過計算來繪制的,但是一直不太準確,最后,使用了這種方法

  2. 第二種就是通過計算啦,已知一個圓的圓心,半徑,以及旋轉(zhuǎn)的角度,求圓上的一點,哈哈,應(yīng)該還是挺好算的呀

     //4---繪制圓弧上的小圓球--根據(jù)currentAngle
     /**
      * Paint.Style.FILL    :填充內(nèi)部
      Paint.Style.FILL_AND_STROKE  :填充內(nèi)部和描邊
      Paint.Style.STROKE  :僅描邊
      */
     canvas.translate(getWidth()/2, getHeight()/2);//這時候的畫布已經(jīng)移動到了中心位置
     Paint paintCircle = new Paint();
     paintCircle.setStyle(Paint.Style.FILL);//設(shè)置填充樣式
     paintCircle.setAntiAlias(true);//抗鋸齒功能
     paintCircle.setColor(Color.WHITE);
     //        paintCircle.setStrokeWidth(borderLitalWidth);//設(shè)置畫筆寬度
    
     //            canvas.drawCircle((float)(centerX+padding-centerX*Math.cos(currentAngle)), (float)(centerX+padding-centerX*Math.sin(currentAngle)), DEFAULT_LITLE_WIDTH, paintCircle);//畫圓,圓心在中心位置,半徑為長寬小者的一半
     //        canvas.drawCircle(0, 0, DEFAULT_LITLE_WIDTH, paintCircle);//這時候的畫布已經(jīng)移動到了中心位置
     /**
     * 第一個參數(shù)為正則順時針,否則逆時針
     * 后面兩個參數(shù)是圓心
     * 畫布的旋轉(zhuǎn)一定要在,畫圖形之前進行旋轉(zhuǎn)
     */
     //        canvas.rotate(-currentAngle, centerX+padding, centerX+padding);//以圓心旋轉(zhuǎn)的
     //        canvas.drawCircle(padding,centerX+padding,DEFAULT_LITLE_WIDTH,paintCircle);
     //        canvas.rotate(currentAngle, centerX+padding, centerX+padding);//以圓心旋轉(zhuǎn)的
     canvas.rotate(currentAngle);
     canvas.drawCircle(-centerX,0,DEFAULT_LITLE_WIDTH,paintCircle);
     canvas.rotate(-currentAngle);
    

最后在加上動畫就ok啦

/**
* 為進度設(shè)置動畫
* @param last
* @param current
*/
private void setAnimation(float last, float current, int length) {
progressAnimator = ValueAnimator.ofFloat(last, current);
Log.e("last=====",last+"");
Log.e("current=====",current+"");
progressAnimator.setDuration(length);
// progressAnimator.setTarget(currentAngle);
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            currentAngle= (float) animation.getAnimatedValue();
            invalidate();
        }
    });
    progressAnimator.start();
}

注意:

    第一個參數(shù)是需要動畫的起始位置角度,
    第二個參數(shù)是最終的位置角度,
    第三個參數(shù)是,運動的速度

項目已經(jīng)上傳到github上了,歡迎大家start和fork

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

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

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