Android自訂視圖與自訂屬性

來源:互聯網
上載者:User

標籤: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裡面加入我們的自訂控制項

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:paddingBottom="@dimen/activity_vertical_margin"    tools:context=".MainActivity">    <com.shaw.uitest.CrossView        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <com.shaw.uitest.CrossView        android:layout_marginTop="10dp"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

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

向自訂視圖中添加自訂屬性

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

聲明屬性

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

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="cross">        <attr name="android:color" />        <attr name="rotation" format="string" />    </declare-styleable></resources>

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

<?xml version="1.0" encoding="utf-8"?><resources>    <attr name="test" format="string" />    <declare-styleable name="foo">        <attr name="test" />    </declare-styleable>    <declare-styleable name="bar">        <attr name="test" />    </declare-styleable></resources>

也可以給屬性建立自訂值, 例如

<attr name="enum_attr">    <enum name="value1" value="1" />    <enum name="value2" value="2" /></attr><attr name="flag_attr">    <flag name="flag1" value="0x01" />    <flag name="flag2" value="0x02" /></attr>

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配置:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:crossview="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:paddingBottom="@dimen/activity_vertical_margin"    tools:context=".MainActivity">    <com.shaw.uitest.CrossView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        crossview:rotation="30"        android:color="#ff0000ff"/>    <com.shaw.uitest.CrossView        android:layout_marginTop="10dp"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        crossview:rotation="50"        android:color="#ff00ff00"/></LinearLayout>

上面聲明了所有以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();    }

我們的旋轉中心是畫布中心, 而不是左上方.
現在運行這個程式, 如下:

Android自訂視圖與自訂屬性

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.