Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View之ProgressBar出場記

Android自定義View之ProgressBar出場記

編輯:關於Android編程

今天我們來看看自定義ProgressBar,在這個過程中,我們順便來看看自定義View中兩個非常關鍵的方法,一個是View的測量,還有一個是自定義屬性。OK,廢話不多說,先來看一張效果圖:

\

 

OK,動手吧。

1.准備工作

寫一個類繼承自View,先來聲明變量,看看我們需要哪些變量:

 

    /**
     * View默認的寬
     */
    private static final int DEFAULTWIDTH = 100;
    /**
     * View默認的高度
     */
    private static final int DEFAULTHEIGHT = 100;
    /**
     * 外層圓圈的線條寬度
     */
    private int stoke = 7;
    /**
     * 外層圓圈的線條顏色
     */
    private int circleColor = Color.BLACK;
    /**
     * 內外圓圈之間的間距
     */
    private int padding = 20;
    /**
     * 內層實體圓的顏色
     */
    private int sweepColor = Color.RED;
    /**
     * 開始繪制的角度
     */
    private int startAngle = -90;
    /**
     * 已經繪制的角度
     */
    private int sweepAngle = 0;
    /**
     * 每次增長的度數
     */
    private int sweepStep = 1;
    /**
     * 畫筆
     */
    private Paint paint;
    /**
     * 繪制扇形需要的矩形
     */
    private RectF rectF;

OK,就這麼幾個變量,都很簡單。接下來我們再看看構造方法,在構造方法中我只需要對畫筆進行簡單的初始化即可,如下(請大家注意構造方法的調用方式,如有疑問請查看之前自定義View的博客):

 

 

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

    public MyProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setAntiAlias(true);
    }

 

2.View繪制

接下來我們來看看View的繪制,大家看上面的效果圖就知道,我們這裡的繪制一共有兩部分,一部分是外部圓環的繪制,還有一部分是內部扇形的繪制,那我們一步一步來:

1.繪制圓環

圓環的繪制很簡單,直接看代碼:

 

        //設置圓環的顏色
        paint.setColor(circleColor);
        //設置圓環的寬度
        paint.setStrokeWidth(stoke);
        //設置繪制模式為描邊
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, (float) (getWidth() / 2 - Math.ceil(stoke / 2.0)), paint);

這裡有一個值得說一下的地方,drawCircle方法有四個參數,前兩個是圓環的中心點的坐標,第三個是半徑,本來半徑是View寬度的一半即可,但是由於系統在繪制的過程中圓環線條寬度的一半算在半徑中,另一半不算在半徑中,所以這裡的半徑我們要適當縮小。

 

2.繪制扇形

扇形的繪制需要我們先構造一個RectF類(當然這個並不是必須的操作),然後就可以開始繪制了,如下:

 

        //設置扇形的顏色
        paint.setColor(sweepColor);
        //設置扇形的繪制風格
        paint.setStyle(Paint.Style.FILL);
        //構造一個RectF 出來,扇形繪制在該RectF中
        rectF = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
        //繪制扇形
        //四個參數分別是扇形所在的矩形,開始繪制的角度,需要繪制的角度,扇形是否和矩形共用一個中心點,畫筆
        canvas.drawArc(rectF, startAngle, sweepAngle, true, paint);
        //增加要繪制的角度
        sweepAngle += sweepStep;
        //如果要繪制的角度大於360度,就從0重新開始繪制
        sweepAngle = sweepAngle > 360 ? 0 : sweepAngle;
        invalidate();

 

OK,做好上面這幾步之後,我的一個自定義ProgressBar基本上就顯示出來了。這個時候我只需要在布局文件中添加上這個自定義控件即可,如下:

 

    

但是我在布局文件中添加自定義控件的時候只能給它一個固定的寬和高,如果給一個wrap_content或者match_parent那麼我的自定義ProgressBar就會顯示不正常,這個問題該怎麼解決呢?這裡就涉及到了一個新的知識,那就是View的測量。

 

3.View測量

當我們自定義一個View的時候,除了重寫onDraw方法之外,還有一個方法有時候也需要我們重寫,那就是onMeasure,onMeasure方法接收兩個參數,分別是widthMeasureSpec和heightMeasureSpec,這兩個參數我們稱作測量規格,它們是一個32位的整型數據,這個數據中高2位表示View的測量模式,低30位表示View的測量值,測量模式分為3種,分別是:

1.EXACTLY:精確模式,對應我們在布局文件中設置寬高時給一個具體值或者match_parent

2.AT_MOST:最大值模式:對應設置寬高時給一個wrap_content

3.UNSPECIFIED:這種測量模式多用在ScrollView中

OK,了解了這些之後,接下來我們就來看看怎麼樣從widthMeasureSpec和heightMeasureSpec中提取出來寬高對應的測量模式與測量值。在MeasureSpec類中提供了兩個靜態方法,分別是getMode和getSize,只要我們將寬高的測量規格傳遞進去就可以獲取它的測量模式和測量值。如下:

 

        //獲取寬的測量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //獲取寬的測量值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //獲取高的測量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //獲取高的測量值
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

拿到寬高的測量模式和測量值之後,我們就可以做一個簡單的處理了,大家已經知道測量模式一共分為三種,如果用戶明確指定了View的寬和高那我就不去管它,如果用戶給了一個wrap_content或者使用了第三種測量模式的話,那我就給View一個默認的寬和高,OK,就這麼一個簡單的邏輯,我們來看看代碼:

 

 

        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                //如果寬為wrap_content,則給定一個默認值
                widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());
                break;
        }

OK,最後,由於我的ProgressBar是繪制一個圓,因此View的寬高必須是相同的,所以再添加一行代碼:

 

 

widthSize = heightSize = Math.min(widthSize, heightSize);
OK,至此,我的View的寬高都確定下來了,最後我只需要調用setMeasuredDimension方法,告訴系統我的測量結果即可,如下:

 

 

setMeasuredDimension(widthSize, heightSize);

所以,一個完整的onMeasure方法應該是下面這個樣子:

 

 

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取寬的測量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //獲取寬的測量值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //獲取高的測量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //獲取高的測量值
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                //如果寬為wrap_content,則給定一個默認值
                widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());
                break;
        }
        widthSize = heightSize = Math.min(widthSize, heightSize);
        //設置測量結果
        setMeasuredDimension(widthSize, heightSize);
    }

OK,從此以後,你就可以在布局文件中給ProgressBar設置任意的寬和高了。

 

4.自定義屬性

做完上面這幾步,我的自定義ProgressBar已經完成的差不多了,現在我如果想要修改圓環的顏色,圓環的線條的寬度,扇形的顏色等等這些屬性的話只能在代碼中修改,可是如果我想要在布局文件中來配置這些顏色,然後在代碼中讀取這些顏色再設置給paint又該怎麼辦呢?這裡就涉及到我們的自定義屬性了。OK,那麼接下來我們就來看看自定義屬性。

自定義屬性需要我們首先在res/values文件夾中添加attrs文件(該文件名可以任意取,約定俗成取attrs),在attrs文件中來生命你要設置的屬性,如下:

 



    
        
        
        
        
        
        
    

首先我們要聲明declare-styleable,給它取一個名字,這個name可以任意取,但是強烈建議取自定義View的類名,因為只有取類名,一會你在布局文件中添加這些屬性時系統才會有提示。OK,裡面的attr節點就是我們定義的一個個的屬性了,name表示屬性的顏色,format表示屬性的取值,format取值主要有如下幾種:

 

1. boolean 屬性取值為boolean類型
2. string 屬性取值為文本類型
3. color 屬性取值為顏色類型
4. dimension 屬性值為尺寸
5. enum 屬性取值是枚舉類型,例如:LinearLayout中的android:orientation="horizontal"屬性
6. flag 屬性取值進行或運算,比如android:layout_gravity="left|bottom"
7. fraction 屬性取值為小數
8. float 屬性取值為浮點數
9. integer 屬性取值為整數
10. reference 屬性取值可以引用一個值

OK,這一部分的工作完成之後,接下來我們就可以在布局文件中設置屬性了,如下:

 

    

 

OK,但是光這樣肯定不行,我只是在布局文件中設置了,代碼裡又該怎麼樣來獲取布局文件中設置的值呢?修改構造方法如下:

    public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setAntiAlias(true);
        //讀取布局文件中設置的屬性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyProgressBar);
        //讀取布局文件中定義的顏色值,第二個參數為默認值(如果布局文件中未設置該屬性時使用)
        circleColor = ta.getColor(R.styleable.MyProgressBar_circleColor, this.circleColor);
        sweepColor = ta.getColor(R.styleable.MyProgressBar_sweepColor, this.sweepColor);
        startAngle = ta.getInt(R.styleable.MyProgressBar_startAngle, this.startAngle);
        sweepStep = ta.getInt(R.styleable.MyProgressBar_sweepStep, this.sweepStep);
        stroke = (int) ta.getDimension(R.styleable.MyProgressBar_stroke, stroke);
        padding = (int) ta.getDimension(R.styleable.MyProgressBar_mypb_padding, padding);
        //回收ta
        ta.recycle();
    }
當系統調用構造方法的時候,我們將布局文件中設置的屬性一個個讀取出來,如果用戶設置了該值,那麼直接讀取出來使用,如果用戶沒有設置該值,那麼我們也給了一個默認的值(默認值就是我們一開始預定義的值),OK,我們再來看看顯示效果:

 

\

 

OK,就是這麼簡單。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved