Android自定義控制元件實現一個帶文字與數字的圓形進度條

王世暉發表於2016-05-09

實現的效果圖如下所示:


第一步:繪製下方有缺口的空心圓,稱為外圍大弧吧

anvas.clipRect(0, 0, mWidth, mHeight / 2 + radius - textHeight * 3 / 4);
第二步:計算繪製圓弧進度條時的起始角度,設定為外圍大弧的左端點為進度值得起點,掃過的角度所佔外圍大弧的百分比就是進度值

第三步:繪製數字、文字、百分號

第四步:使用Handler Runnable 和DecelerateInterpolator是進度條和數字動起來

測試程式碼:

final CustomCircleBar circle=(CustomCircleBar)findViewById(R.id.win_home);
circle.setPercent(10);
circle.setCustomText("呵呵");
circle.setProgessColor(getResources().getColor(R.color.blue));
final Random random=new Random();
circle.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v){
        circle.setPercent(random.nextInt(100));
    }
});
完成程式碼如下:

public class CustomCircleBar extends View {
    private Context context;
    /*進度值*/
    private int percent;
    /*顏色值*/
    private int mProgessColor;
    /*下邊的文字名稱*/
    private String mCustomText;
    /*外圈圓環的畫筆*/
    private Paint paintBar = new Paint();
    /*下邊文字的畫筆*/
    private Paint paintText = new Paint();
    /*動態獲取屬性值*/
    private TypedValue typedValue;
    /*先加速後減速*/
    DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
    /*動畫持續時間*/
    private int duration = 10;
    private int curTime = 0;
    public CustomCircleBar(Context context) {
        super(context);
        this.context=context;
        init();
    }

    public CustomCircleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        init();
    }

    public CustomCircleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context=context;
        init();
    }



    public void setPercent(int percent) {
        this.percent = percent;
        /*isShown():Returns the visibility of this view and all of its ancestors*/
        if (isShown()) {
            /*設定進度後重新開始一次動畫*/
            curTime=0;
            this.invalidate();
        }
    }

    public void setProgessColor(int mProgessColor) {
        this.mProgessColor = mProgessColor;
        if (isShown()) {
            this.invalidate();
        }
    }


    public void setCustomText(String mCustomText) {
        this.mCustomText = mCustomText;
    }

    private Handler mHandler = new Handler();
    private Runnable mAnimation = new Runnable() {
        @Override
        public void run() {
            if (curTime < duration) {
                curTime++;
                /*導致重繪,呼叫onDraw,onDraw最後呼叫
                *  mHandler.postDelayed(mAnimation, 20);更新進度條,介面重繪
                *  每次20毫秒,繪製10次,因此動畫時間200毫秒*/
                CustomCircleBar.this.invalidate();
            }
        }
    };

    private void init() {
        /*資料初始化,沒有設定屬性時候的預設值*/
        percent = 0;
        mProgessColor=Color.rgb(95,112,72);
        mCustomText="Home";
        typedValue=new TypedValue();
        context.getTheme().resolveAttribute(R.attr.maintextclor,typedValue,true);
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float mWidth = getWidth();
        float mHeight = getHeight();
        /*下邊是進度條畫筆的設定*/
        /** Restores the paint to its default settings. */
        paintBar.reset();
        /*圓環寬度4個畫素*/
        paintBar.setStrokeWidth(4);
        /*空心圓環而非填充的額扇形*/
        paintBar.setStyle(Paint.Style.STROKE);
        paintBar.setAntiAlias(true);
        paintBar.setColor(mProgessColor);
        /*調整下不透明度,使邊框弧和進度條區分開*/
        paintBar.setAlpha(80);
        /*接下來是文字畫筆的設定*/
        paintText.setTextSize(20);
        paintText.setColor(getResources().getColor(typedValue.resourceId));
        paintText.setStyle(Paint.Style.STROKE);
        paintText.setAntiAlias(true);
        /*從中間開始繪製文字*/
        paintText.setTextAlign(Paint.Align.CENTER);
        /*測量文字大小*/
        Paint.FontMetrics fontMetrics = paintText.getFontMetrics();
        /*計算文字高度*/
        float textHeight = fontMetrics.bottom - fontMetrics.top;
        /*計算圓的半徑*/
        float radius = Math.min(mWidth, mHeight) / 2 - 10;
        /*  ❑ save:用來儲存Canvas的狀態。save之後,可以呼叫Canvas的平移、放縮、旋轉、錯切、裁剪等操作。
            ❑ restore:用來恢復Canvas之前儲存的狀態。防止save後對Canvas執行的操作對後續的繪製有影響。*/
        /*儲存畫布,繪製進度條*/
        canvas.save();
        /*clipRect:該方法用於裁剪畫布,也就是設定畫布的顯示區域
        呼叫clipRect()方法後,只會顯示被裁剪的區域,之外的區域將不會顯示 */
        canvas.clipRect(0, 0, mWidth, mHeight / 2 + radius - textHeight * 3 / 4);
        /*因為clipRect的原因,外邊的圓環下邊留個缺口繪製文字*/
        canvas.drawCircle(mWidth / 2, mHeight / 2, radius, paintBar);

        /*三角函式計算,下方缺口扇形的角度的一半*/
        float theta_offset = (float) Math.acos((radius - textHeight / 2) / radius);
        /*大弧圍成的扇形的角度*/
        float theta_full = 360 - 2 * theta_offset;
        /*進度值圍成的弧對應的角度*/
        float thetaProcess = mDecelerateInterpolator.getInterpolation(1.0f * curTime / duration) * percent * theta_full / 100;
        /*設定進度值顏色完全不透明*/
        paintBar.setAlpha(255);
        paintBar.setColor(mProgessColor);
        /*注意弧形的起始角度,下邊因顯示文字導致圓環斷開成一條弧,弧有左右兩個端點,從左端點開始畫弧*/
        canvas.drawArc(new RectF(mWidth / 2 - radius, mHeight / 2 - radius, mWidth / 2 + radius, mHeight / 2 + radius), theta_offset+90, thetaProcess, false, paintBar);
        /*恢復畫布*/
        canvas.restore();
        /*開始繪製文字*/
        paintText.setTextSize(20);
        fontMetrics = paintText.getFontMetrics();
        float textBaseLineOffset = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        canvas.drawText(mCustomText, mWidth / 2, mHeight / 2 + radius - textHeight / 2 + textBaseLineOffset, paintText);

        /*繪製百分號*/
        paintText.setTextSize(mHeight * 1 / 8);
        fontMetrics = paintText.getFontMetrics();
        textBaseLineOffset = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        canvas.drawText("%", mWidth / 2, mHeight / 2 + radius / 3 + textBaseLineOffset, paintText);

        /*繪製百分比*/
        paintText.setTextSize(mHeight * 3 / 8);
        canvas.drawText("" + (int)(percent*mDecelerateInterpolator.getInterpolation(1.0f * curTime / duration)), mWidth / 2, mHeight / 2, paintText);
        /*20毫秒後執行動畫*/
        mHandler.postDelayed(mAnimation, 20);
    }
}



相關文章