Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義視圖與自定義屬性

Android自定義視圖與自定義屬性

編輯:關於Android編程

這是Android UI Fundamentals裡的內容

創建自定義視圖

創建自定義UI組件首先要繼承一個視圖類.
首先創建一個簡單的自定義視圖, 展示一條十字線.
十字線截圖
需要做的第一件事是創建一個繼承自View的CrossView類.

    public CrossView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

該構造函數的第二個參數是用來傳遞XML參數的, 等會兒會講到. 接下來我們要重寫兩個基礎方法: onMeasureonDraw.

onMeasure

系統調用onMeasure方法來決定視圖及其子視圖的尺寸. 它的兩個參數的類型都是int, 但是這倆參數並非普通的數字, 而是兩個MeasureSpec, MeasureSpec是一個模式和一個整型尺寸值的結合, 被當成一個整數來實現. 其中模式值有如下幾種情況:

模式 解釋 UNSPECIFIED 父視圖沒有在這個視圖上做任何限制, 它可以是任意尺寸 AT_MOST 該視圖可以是小於等於MeasureSpec中尺寸的任意大小 EXACTLY 父視圖要求該視圖必須是MeasureSpec指定的尺寸大小

當你創建一個自定義視圖並重寫onMeasure方法時, 必須正確處理每種情況, 得到相應的尺寸, 然後必須在onMeasure中調用setMeasureDimensions方法, 參數就是你決定的尺寸, 如果不調用就會拋出異常.
下面是重寫的onMeasure方法代碼.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(calculateMeasure(widthMeasureSpec), calculateMeasure(heightMeasureSpec));
    }

注意其中calculateMeasure方法是我們自己定義的, 下面我們來完成這個方法.
我們先定義一個默認的尺寸100, 單位是dp(我暫時不確定是不是dp).

private static final int DEFAULT_SIZE = 100;

乘上設備的像素密度, 得到實際顯示需要的像素值.

int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);

然後我們需要從MeasureSpec中拿到模式和尺寸

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

接下來我們根據specMode的情況來判斷result的值到底應該是什麼.

MeasureSpec.UNSPECIFIED
此時父控件對自定義視圖的尺寸沒有要求, 那麼我就以默認大小為結果, 也就是說
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
MeasureSpec.AT_MOST
此時父控件認為最多不能超過指定尺寸值, 那麼此時我們選指定值和默認值中最小的那個就行, 無論哪種情況這種選法都是合法的.
result = Math.min(specSize, result);
MeasureSpec.EXACTLY
此時父控件要求子視圖必須是給定的尺寸, 那麼我們讓result等於它就好
result = specSize;

綜合上面的討論, 最終我們的方法代碼如下:

    private static final int DEFAULT_SIZE = 100;

    private int calculateMeasure(int measureSpec) {
        int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(specSize, result);
        }
        return result;
    }

onDraw

當視圖應當繪制其內容時會調用onDraw方法. 在重寫它之前, 我們先創建一個Paint對象, 它處理諸如顏色和文本大小之類的事情.
通過CrossView的構造函數來創建Paint對象

    private Paint mPaint;

    public CrossView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(0xffff0000);
    }

上面的代碼新建了Paint對象, 並設置抗鋸齒和顏色.
接下來重寫onDraw方法, 模板如下, canvas.save()canvas.restore()我就不解釋了, 不影響後面的理解.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        // code goes here
        canvas.restore();
    }

我們基於視圖的尺寸縮放畫布, 這樣我們可以使用0到1之間的浮點數來作為畫線時的坐標

    private static final float[] mPoints = {0.5f, 0f,
                                            0.5f, 1f,
                                            0f, 0.5f,
                                            1f, 0.5f};

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.scale(getWidth(), getHeight());
        canvas.drawLines(mPoints, mPaint);
        canvas.restore();
    }

我們在activity的xml裡面加入我們的自定義控件




    

    

運行一下就可以看到文章開頭的截圖畫面了.

向自定義視圖中添加自定義屬性

有了自定義視圖, 我們希望它能通過自定義XML屬性來配置, 要做到這一點, 需要先聲明屬性, 然後在XML布局中添加一個新的命名空間, 最後處理被傳遞給自定義視圖構造函數的AttributeSet對象.

聲明屬性

在res/values/目錄下創建一個attrs.xml(可以是別的名字)的文件, 然後在其中添加如下內容:



    
        
        
    

declare-styleable元素有一個name屬性, 用來在代碼中的引用自定義屬性, 每個自定義的屬性都使用一個attr元素來聲明, attr元素有name和format兩個屬性, name用於引用, format代表它的數據類型, 如果使用了默認的系統屬性, 就不需要定義format了, 如果嘗試給已有的屬性定義一個不同的format, 則工程無法build. 在外層聲明的attr可以被其他declare-styleable復用, 就和使用系統屬性一樣, 比如:



    
    
        
    
    
        
    

也可以給屬性創建自定義值, 例如


    
    


    
    

enumflag都要求是整數. 不同之處在於flag可以使用|來拼接. 比如android:gravity的值就是flag.

在XML中使用屬性

要使用在我們的XML中的新屬性, 首先必須為視圖聲明namespace. 其實我們經常見到namespace的聲明, 比如我們常在activity的xml文件中看到

xmlns:android="http://schemas.android.com/apk/res/android"

這個namespace聲明了所有以關鍵詞android開頭的屬性都可以在android包中找到. 要使用自定義屬性, 需要聲明一個帶有新包名的新namespace, 下面為CrossView的屬性添加一個新的namespace, 並在自定義視圖中添加相關的xml配置:




    

    

上面聲明了所有以crossview(名字可以用別的)開頭的屬性都可以在res中找到. 這是Gradle要求的寫法.

在代碼中使用XML屬性

CrossView的構造函數中傳入了一個AttributeSet對象, 我們可以通過它獲取XML布局中聲明的屬性.
更新CrossView的構造函數並添加相應函數和成員變量:

    private float mRotation;

    public CrossView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.cross);
        int color = arr.getColor(R.styleable.cross_android_color, Color.BLACK);
        float rotation = arr.getFloat(R.styleable.cross_rotation, 0f);
        arr.recycle();
        setColor(color);
        setRotation(rotation);
    }

    public void setColor(int color) {
        mPaint.setColor(color);
    }

    public void setRotation(float degree) {
        mRotation = degree;
    }

同時更新onDraw的代碼

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.scale(getWidth(), getHeight());
        canvas.rotate(mRotation, 0.5f, 0.5f);
        canvas.drawLines(mPoints, mPaint);
        canvas.restore();
    }

我們的旋轉中心是畫布中心, 而不是左上角.
現在運行這個程序, 截圖如下:
自定義屬性的視圖

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