1. 程式人生 > >自定義一個帶進度值的圓形進度條

自定義一個帶進度值的圓形進度條

專案中有時候我們為了博得使用者的眼球,需要自定義一些好看的控制元件,下面記錄一個自定義帶進度值的圓形進度條

先上效果


下面記錄具體的實現過程

在Android studio下新建一個project,然後新建一個CircleProgressView,繼承系統的view,然後重寫它的三個構造方法,如下:

    public CircleProgressView(Context context) {
        this(context,null);
    }
    public CircleProgressView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
    }
然後在定義圓形角度條需要的一些引數:
    private Context mContext;
    private Paint mBackgroudPaint;//圓的畫筆
    private Paint mFrontPaint;//進度條的畫筆
    private Paint mTextPaint;//數字的畫筆
    private int mWidth;//控制元件的寬度
    private int mHeight;//控制元件的高度
    private  float redis = 200;//預設圓的半徑
    private float roundWidth = 50;//預設圓形進度的寬度
    private float textSize = 50;//預設字型的大小
    private int progress = 0;//記錄進度值
    private int maxProgress = 100;//最大進度值
    private int backProgressColor = 0xff778899;//預設背景的顏色
    private int roundProgressColor = 0xff00BFFF;//預設圓形進度條的顏色
    private int textColor = 0xff008080;//預設字型的顏色
    private int mXCenter;//圓心的x座標
    private int mYCenter;//圓心的Y座標
我們先不自定義屬性,先在程式碼中使用預設的值。寫一個方法,得到手機的中心點,我們要以這個中心點繪製圓
 /**
     * 得到螢幕的中心點
     */
    private void getPhoneCenter(){
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        mXCenter = wm.getDefaultDisplay().getWidth()/2;
        mYCenter = wm.getDefaultDisplay().getHeight()/2;
    }
然後在構造方法中呼叫getPhoneCenter()方法,獲取到中心點的座標。

得到中心點的座標後,就可以開始繪製了,重寫onDraw()方法,在onDraw()方法中,我們分別繪製背景圓,進度條的扇形,和進度值的數字

先繪製背景圓:

        mBackgroudPaint = new Paint();//初始化背景畫筆
        mBackgroudPaint.setColor(backProgressColor);
        mBackgroudPaint.setAlpha(100);//透明度   0~255  從完全透明到不透明
        mBackgroudPaint.setAntiAlias(true);//消除鋸齒
        mBackgroudPaint.setStyle(Paint.Style.FILL);//實心圓
        canvas.drawCircle(mXCenter,mYCenter,redis,mBackgroudPaint);//制定中心點的座標,半徑

然後在繪製進度值的數字
        mTextPaint = new Paint();//初始化數字的畫筆
        mTextPaint.setColor(textColor);//
        mTextPaint.setAntiAlias(true);//消除鋸齒
        mTextPaint.setTextSize(textSize);//
        mTextPaint.setTextAlign(Paint.Align.CENTER);//設定文字的對齊方式
        canvas.drawText(progress+"%",mXCenter,mYCenter, mTextPaint);//progress是載入的進度值
最後就是繪製進度條了
        mFrontPaint = new Paint();
        mFrontPaint.setColor(roundProgressColor);
        mFrontPaint.setAntiAlias(true);//消除鋸齒
        mFrontPaint.setStyle(Paint.Style.STROKE);//空心圓
        mFrontPaint.setStrokeWidth(roundWidth);
        mFrontPaint.setAlpha(200);//透明度 
        //確定一個矩形的區域
        RectF oval = new RectF(mXCenter-redis+roundWidth/2,mYCenter-redis+roundWidth/2,mXCenter+redis-roundWidth/2,mYCenter+redis-roundWidth/2);
        progress = (int)((progress*1.0/maxProgress)*360);
        /**
         * 繪製扇形
         * 第一個引數:確定的矩形區域
         * 第二個引數:開始繪製的角度,從12點中開始繪製,角度為-90°
         * 第三個引數:是否把弧形的2個點連線起來,false:不連線
         * 第四個引數:畫筆
         */
        canvas.drawArc(oval,-90,progress,false,mFrontPaint);

然後我們在MainActivity對應的xml檔案中新增自定義的CircleProgressView控制元件(此處的程式碼省略)。
我們在MainActivity中啟用一個執行緒來模擬下載,並結合Handle來處理模擬的結果
class MyThread extends Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                Message msg = myHandler.obtainMessage();
                msg.what = UPDATE_PROGRESS;
                msg.obj = i+1;
                myHandler.sendMessage(msg);
                try {
                    Thread.sleep(100);//休眠100ms
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

Handle接受到每次接受到訊息以後,呼叫CircleProgressView中的setProgress方法,設定進度的變化
private final int UPDATE_PROGRESS = 001;
    CircleProgressView circleProgressView = null;

     Handler myHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 001:
                    int progress =(int) msg.obj;
                    circleProgressView.setProgress(progress);
                    break;
            }
        }
    };

那我們就要在CircleProgressView中給progress新增set,get方法,在set方法中,判斷當前的progress是否大於最大值,如果不大於,在重新繪製view
public void setProgress(int progress) {
        this.progress = progress;
        if(progress<=maxProgress){//判斷progress是否大於maxProgress
            invalidate();//呼叫這個方法,會重新繪製
        }
    }
    public int getProgress() {
        return progress;
    }

先看一下效果:


需要注意的是:這個時候關於圓形進度控制元件的屬性我們都是在程式碼中寫死的,這樣肯定不能達到複用的目的,我們需要把其中一些關鍵的屬性抽取出來,像系統控制元件的屬性那樣可以在xml中配置

在res/value檔案下新建一個attrs.xml的樣式檔案,定義以下屬性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundProgressBar">
        <attr name="roundColor" format="color"/> <!-- 背景色-->
        <attr name="roundProgressColor" format="color"/><!-- 進度條的顏色-->
        <attr name="roundWidth" format="dimension"></attr><!-- 進度條的寬度-->
        <attr name="textColor" format="color" /><!-- 字型的顏色-->
        <attr name="textSize" format="dimension" /><!-- 字型的大小-->
    </declare-styleable>
</resources>

然後我們在CircleProgressView的建構函式中獲取這些屬性,並且賦值。

自定義一個方法,initResource(attrs)獲取自定義的屬性值,並在建構函式中呼叫

    private void initResource(AttributeSet attrs) {
        //獲取我們自定義的檔名,RoundProgressBar是attrs中定義的name
        TypedArray mTypeArray = mContext.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar,0,0);
        //獲取背景色,如果沒有設定則使用預設
        backProgressColor = mTypeArray.getColor(R.styleable.RoundProgressBar_roundColor,backProgressColor);
        //獲取進度條顏色,如果沒有設定則使用預設
        roundProgressColor = mTypeArray.getColor(R.styleable.RoundProgressBar_roundProgressColor,roundProgressColor);
        //獲取背景圓的半徑,如果沒有設定則使用預設
        redis = mTypeArray.getDimension(R.styleable.RoundProgressBar_roundWidth,redis);
        //進度條的寬度設定為半徑的1/5
        roundWidth = redis/5;
        //獲取文字的顏色,如果沒有設定則使用預設
        textColor = mTypeArray.getColor(R.styleable.RoundProgressBar_textColor,textColor);
        //獲取文字的大小,如果沒有設定則使用預設
        textSize = mTypeArray.getDimension(R.styleable.RoundProgressBar_textSize,textSize);
    }
然後我們重寫onMeasure()方法,測量控制元件的大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getRealSize(widthMeasureSpec);
        mHeight = getRealSize(heightMeasureSpec);
    }

自定義一個getRealSize()的方法,得到控制元件的大小
    private int getRealSize(int measureSpec){
        float res = -1;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if(mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED){
            res = redis*2;
        }else{
            res = size;
        }
        return (int)res;
    }

到此程式碼寫的已經差不多了。這兒需要注意:

在eclipse中使用自定義屬性,我們要加上

xmlns:app="http://schemas.android.com/apk/res/我們自己定義的view的包名"
但是在Android studio中使用的時候這麼寫會報錯,提示找不到,我們直接讓它自動搜尋,需要這麼寫
xmlns:app ="http://schemas.android.com/apk/res-auto"

最後就是在xml中對自定義屬性的使用了

   <main.zhaocd.com.circleprogressview.CircleProgressView
        android:id="@+id/my_view"
        android:layout_width="600dp"
        android:layout_height="600dp"
        app:roundWidth="80dp"
        app:roundColor="#7f8d9c"
        app:roundProgressColor="#07ce7e"
        app:textColor="#e21e17"
        app:textSize="40dp"
        />

最後在總結一下,一般自定義view的步驟:

1、建立attrs檔案,自定義屬性(如果一開始不能很好的把握需要那些屬性,可以把這一步放到最後)

2、在構造方法中獲取自定義的屬性

3、重寫onMeasure()方法(測量控制元件的大小)

4、重寫onDraw()方法(具體的繪製,呼叫invalidate()方法會強制執行onDraw方法)