Android自定義View(二、深入解析自定義屬性)

轉載請標明出處:
http://blog.csdn.net/xmxkf/article/details/51468648
本文出自:【openXu的博客】

目錄:

  在上一篇博客《Android自定義View(一、初體驗)》中我們體驗了自定義控件的基本流程:

  1. 繼承View,覆蓋構造方法
  2. 自定義屬性
  3. 重寫onMeasure方法測量寬高
  4. 重寫onDraw方法繪製控件


  接下來幾篇博客分別深入學習每一個步驟的知識點,第一步就不用多講了,這篇博客我們看一看自定義屬性到底是怎麼一個流程,爲什麼要這樣做。

1. 爲什麼要自定義屬性

  要使用屬性,首先這個屬性應該存在,所以如果我們要使用自己的屬性,必須要先把他定義出來才能使用。但我們平時在寫佈局文件的時候好像沒有自己定義屬性,但我們照樣可以用很多屬性,這是爲什麼?我想大家應該都知道:系統定義好的屬性我們就可以拿來用唄,但是你們知道系統定義了哪些屬性嗎?哪些屬性是我們自定義控件可以直接使用的,哪些不能使用?什麼樣的屬性我們能使用?這些問題我想大家不一定都弄得清除,下面我們去一一解開這些謎團。
  系統定義的所有屬性我們可以在\sdk\platforms\Android-xx\data\res\values目錄下找到attrs.xml這個文件,這就是系統自帶的所有屬性,打開看看一些比較熟悉的:

<declare-styleable name="View">
    <attr name="id" format="reference" />
    <attr name="background" format="reference|color" />
    <attr name="padding" format="dimension" />
     ...
    <attr name="focusable" format="boolean" />
     ...
</declare-styleable>

<declare-styleable name="TextView">
    <attr name="text" format="string" localization="suggested" />
    <attr name="hint" format="string" />
    <attr name="textColor" />
    <attr name="textColorHighlight" />
    <attr name="textColorHint" />
     ...
</declare-styleable>

<declare-styleable name="ViewGroup_Layout">
    <attr name="layout_width" format="dimension">
        <enum name="fill_parent" value="-1" />
        <enum name="match_parent" value="-1" />
        <enum name="wrap_content" value="-2" />
    </attr>
    <attr name="layout_height" format="dimension">
        <enum name="fill_parent" value="-1" />
        <enum name="match_parent" value="-1" />
        <enum name="wrap_content" value="-2" />
    </attr>
</declare-styleable>

<declare-styleable name="LinearLayout_Layout">
    <attr name="layout_width" />
    <attr name="layout_height" />
    <attr name="layout_weight" format="float" />
    <attr name="layout_gravity" />
</declare-styleable>

<declare-styleable name="RelativeLayout_Layout">
    <attr name="layout_centerInParent" format="boolean" />
    <attr name="layout_centerHorizontal" format="boolean" />
    <attr name="layout_centerVertical" format="boolean" />
     ...
</declare-styleable>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44


  看看上面attrs.xml文件中的屬性,發現他們都是有規律的分組的形式組織的。以declare-styleable 爲一個組合,後面有一個name屬性,屬性的值爲View 、TextView 等等,有沒有想到什麼?沒錯,屬性值爲View的那一組就是爲View定義的屬性,屬性值爲TextView的就是爲TextView定義的屬性…。

  因爲所有的控件都是View的子類,所以爲View定義的屬性所有的控件都能使用,這就是爲什麼我們的自定義控件沒有定義屬性就能使用一些系統屬性。

  但是並不是每個控件都能使用所有屬性,比如TextView是View的子類,所以爲View定義的所有屬性它都能使用,但是子類肯定有自己特有的屬性,得單獨爲它擴展一些屬性,而單獨擴展的這些屬性只有它自己能有,View是不能使用的,比如View中不能使用android:text=“”。又比如,LinearLayout中能使用layout_weight屬性,而RelativeLayout卻不能使用,因爲layout_weight是爲LinearLayout的LayoutParams定義的。

  綜上所述,自定義控件如果不自定義屬性,就只能使用VIew的屬性,但爲了給我們的控件擴展一些屬性,我們就必須自己去定義。


2. 怎樣自定義屬性

  翻閱系統的屬性文件,你會發現,有的這中形式,有的是;這兩種的區別就是attr標籤後面帶不帶format屬性,如果帶format的就是在定義屬性,如果不帶format的就是在使用已有的屬性,name的值就是屬性的名字,format是限定當前定義的屬性能接受什麼值。

  打個比方,比如系統已經定義了android:text屬性,我們的自定義控件也需要一個文本的屬性,可以有兩種方式:

第一種:我們並不知道系統定義了此名稱的屬性,我們自己定義一個名爲text或者mText的屬性(屬性名稱可以隨便起的)

<resources>
    <declare-styleable name="MyTextView">
        <attr name=“text" format="string" />
    </declare-styleable>
</resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

第二種:我們知道系統已經定義過名稱爲text的屬性,我們不用自己定義,只需要在自定義屬性中申明,我要使用這個text屬性
(注意加上android命名空間,這樣才知道使用的是系統的text屬性)

<resources>
    <declare-styleable name="MyTextView">
        <attr name=“android:text"/>
    </declare-styleable>
</resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

  爲什麼系統定義了此屬性,我們在使用的時候還要聲明?因爲,系統定義的text屬性是給TextView使用的,如果我們不申明,就不能使用text屬性。

3. 屬性值的類型format

format支持的類型一共有11種:
(1). reference:參考某一資源ID

  • 屬性定義:
<declare-styleable name = "名稱">
     <attr name = "background" format = "reference" />
</declare-styleable>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 屬性使用:
<ImageView android:background = "@drawable/圖片ID"/>
  • 1
  • 1

(2). color:顏色值

  • 屬性定義:
<attr name = "textColor" format = "color" />
  • 1
  • 1
  • 屬性使用:
<TextView android:textColor = "#00FF00" />
  • 1
  • 1

(3). boolean:布爾值

  • 屬性定義:
<attr name = "focusable" format = "boolean" />
  • 1
  • 1
  • 屬性使用:
<Button android:focusable = "true"/>
  • 1
  • 1

(4). dimension:尺寸值

  • 屬性定義:
<attr name = "layout_width" format = "dimension" />
  • 1
  • 1
  • 屬性使用:
<Button android:layout_width = "42dip"/>
  • 1
  • 1

(5). float:浮點值

  • 屬性定義:
<attr name = "fromAlpha" format = "float" />
  • 1
  • 1
  • 屬性使用:
<alpha android:fromAlpha = "1.0"/>
  • 1
  • 1

(6). integer:整型值

  • 屬性定義:
<attr name = "framesCount" format="integer" />
  • 1
  • 1
  • 屬性使用:
<animated-rotate android:framesCount = "12"/>
  • 1
  • 1

(7). string:字符串

  • 屬性定義:
<attr name = "text" format = "string" />
  • 1
  • 1
  • 屬性使用:
<TextView android:text = "我是文本"/>
  • 1
  • 1

(8). fraction:百分數

  • 屬性定義:
<attr name = "pivotX" format = "fraction" />
  • 1
  • 1
  • 屬性使用:
<rotate android:pivotX = "200%"/>
  • 1
  • 1

(9). enum:枚舉值

  • 屬性定義:
<declare-styleable name="名稱">
    <attr name="orientation">
        <enum name="horizontal" value="0" />
        <enum name="vertical" value="1" />
    </attr>
</declare-styleable>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 屬性使用:
<LinearLayout  
    android:orientation = "vertical">
</LinearLayout>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

注意:枚舉類型的屬性在使用的過程中只能同時使用其中一個,不能 android:orientation = “horizontal|vertical"

(10). flag:位或運算

  • 屬性定義:
<declare-styleable name="名稱">
    <attr name="gravity">
            <flag name="top" value="0x30" />
            <flag name="bottom" value="0x50" />
            <flag name="left" value="0x03" />
            <flag name="right" value="0x05" />
            <flag name="center_vertical" value="0x10" />
            ...
    </attr>
</declare-styleable>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 屬性使用:
<TextView android:gravity="bottom|left"/>
  • 1
  • 1

注意:位運算類型的屬性在使用的過程中可以使用多個值

(11). 混合類型:屬性定義時可以指定多種類型值

  • 屬性定義:
<declare-styleable name = "名稱">
     <attr name = "background" format = "reference|color" />
</declare-styleable>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 屬性使用:
<ImageView
android:background = "@drawable/圖片ID" />
或者:
<ImageView
android:background = "#00FF00" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

  通過上面的學習我們已經知道怎麼定義各種類型的屬性,以及怎麼使用它們,但是我們寫好佈局文件之後,要在控件中使用這些屬性還需要將它解析出來。

4. 類中獲取屬性值

  在這之前,順帶講一下命名空間,我們在佈局文件中使用屬性的時候(android:layout_width="match_parent")發現前面都帶有一個android:,這個android就是上面引入的命名空間xmlns:android="http://schemas.android.com/apk/res/android”,表示到android系統中查找該屬性來源。只有引入了命名空間,XML文件才知道下面使用的屬性應該去哪裏找(哪裏定義的,不能憑空出現,要有根據)。

  如果我們自定義屬性,這個屬性應該去我們的應用程序包中找,所以要引入我們應用包的命名空間xmlns:openxu="http://schemas.android.com/apk/res-auto”,res-auto表示自動查找,還有一種寫法xmlns:openxu="http://schemas.android.com/apk/com.example.openxu.myview"com.example.openxu.myview爲我們的應用程序包名。

  按照上面學習的知識,我們先定義一些屬性,並寫好佈局文件。
先在res\values目錄下創建attrs.xml,定義自己的屬性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <!--聲明MyTextView需要使用系統定義過的text屬性,注意前面需要加上android命名-->
        <attr name="android:text" />
        <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
    </declare-styleable>
</resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在佈局文件中,使用屬性(注意引入我們應用程序的命名空間,這樣在能找到我們包中的attrs):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:openxu="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
        <com.example.openxu.myview.MyTextView
            android:layout_width="200dip"
            android:layout_height="100dip"
            openxu:mTextSize="25sp"
            android:text="我是文字"
            openxu:mTextColor ="#0000ff"
            android:background="#ff0000"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14


在構造方法中獲取屬性值:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
    String text = ta.getString(R.styleable.MyTextView_android_text);
    int mTextColor = ta.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
    int mTextSize = ta.getDimensionPixelSize(R.styleable.MyTextView_mTextSize, 100);
    ta.recycle();  //注意回收
    Log.v("openxu", “text屬性值:"+mText);
    Log.v("openxu", "mTextColor屬性值:"+mTextColor);
    Log.v("openxu", "mTextSize屬性值:"+mTextSize);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

log輸出:

05-21 00:14:07.192: V/openxu(25652): mText屬性值:我是文字
05-21 00:14:07.192: V/openxu(25652): mTextColor屬性值:-16776961
05-21 00:14:07.192: V/openxu(25652): mTextSize屬性值:75
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

到此爲止,是不是發現自定義屬性是如此簡單?

屬性的定義我們應該學的差不多了,但有沒有發現構造方法中獲取屬性值的時候有兩個比較陌生的類AttributeSetTypedArray,這兩個類是怎麼把屬性值從佈局文件中解析出來的?

5. Attributeset和TypedArray以及declare-styleable

  Attributeset看名字就知道是一個屬性的集合,實際上,它內部就是一個XML解析器,幫我們將佈局文件中該控件的所有屬性解析出來,並以key-value的兼職對形式維護起來。其實我們完全可以只用他通過下面的代碼來獲取我們的屬性就行。

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    int count = attrs.getAttributeCount();
    for (int i = 0; i < count; i++) {
        String attrName = attrs.getAttributeName(i);
        String attrVal = attrs.getAttributeValue(i);
        Log.e("openxu", "attrName = " + attrName + " , attrVal = " + attrVal);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

log輸出:

05-21 02:18:09.052: E/openxu(14704): attrName = background , attrVal = @2131427347
05-21 02:18:09.052: E/openxu(14704): attrName = layout_width , attrVal = 200.0dip
05-21 02:18:09.052: E/openxu(14704): attrName = layout_height , attrVal = 100.0dip
05-21 02:18:09.052: E/openxu(14704): attrName = text , attrVal = 我是文字
05-21 02:18:09.052: E/openxu(14704): attrName = mTextSize , attrVal = 25sp
05-21 02:18:09.052: E/openxu(14704): attrName = mTextColor , attrVal = #0000ff
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  發現通過Attributeset獲取屬性的值時,它將我們佈局文件中的值原原本本的獲取出來的,比如寬度200.0dip,其實這並不是我們想要的,如果我們接下來要使用寬度值,我們還需要將dip去掉,然後轉換成整形,這多麻煩。其實這都不算什麼,更噁心的是,backgroud我應用了一個color資源ID,它直接給我拿到了這個ID值,前面還加了個@,接下來我要自己獲取資源,並通過這個ID值獲取到真正的顏色。

我們再換TypedArray試試。
  在這裏,穿插一個知識點,定義屬性的時候有一個declare-styleable,他是用來幹嘛的,如果不要它可不可以?答案是可以的,我們自定義屬性完全可以寫成下面的形式:

<?xml version="1.0" encoding="utf-8"?>
<resources>
        <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
</resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

之前的形式是這樣的:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <attr name="android:text" />
        <attr name="android:layout_width" />
        <attr name="android:layout_height" />
        <attr name="android:background" />
        <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
    </declare-styleable>
</resources>

或者:
<?xml version="1.0" encoding="utf-8"?>
<resources>
          <!--定義屬性-->
       <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
    <declare-styleable name="MyTextView">
        <!--生成索引-->
        <attr name="android:text" />
        <attr name="android:layout_width" />
        <attr name="android:layout_height" />
        <attr name="android:background" />
        <attr name=“mTextColor" />
        <attr name="mTextSize"  />
    </declare-styleable>
</resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28


  我們都知道所有的資源文件在R中都會對應一個整型常亮,我們可以通過這個ID值找到資源文件。
  屬性在R中對應的類是public static final class attr,如果我們寫了declare-styleable,在R文件中就會生成styleable類,這個類其實就是將每個控件的屬性分組,然後記錄屬性的索引值,而TypedArray正好需要通過此索引值獲取屬性。

public static final class styleable

          public static final int[] MyTextView = {
            0x0101014f, 0x7f010038, 0x7f010039
        };
        public static final int MyTextView_android_text = 0;
        public static final int MyTextView_mTextColor = 1;
        public static final int MyTextView_mTextSize = 2;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9


使用TypedArray獲取屬性值:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
    String mText = ta.getString(R.styleable.MyTextView_android_text);
    int mTextColor = ta.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
    int mTextSize = ta.getDimensionPixelSize(R.styleable.MyTextView_mTextSize, 100);
    float width = ta.getDimension(R.styleable.MyTextView_android_layout_width, 0.0f);
    float hight = ta.getDimension(R.styleable.MyTextView_android_layout_height,0.0f);
    int backgroud = ta.getColor(R.styleable.MyTextView_android_background, Color.BLACK);
    ta.recycle();  //注意回收
    Log.v("openxu", "width:"+width);
    Log.v("openxu", "hight:"+hight);
    Log.v("openxu", "backgroud:"+backgroud);
    Log.v("openxu", "mText:"+mText);
    Log.v("openxu", "mTextColor:"+mTextColor);
    Log.v("openxu", "mTextSize:"+mTextSize);ext, 0, mText.length(), mBound);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

log輸出:

05-21 02:56:49.162: V/openxu(22630): width:600.0
05-21 02:56:49.162: V/openxu(22630): hight:300.0
05-21 02:56:49.162: V/openxu(22630): backgroud:-12627531
05-21 02:56:49.162: V/openxu(22630): mText:我是文字
05-21 02:56:49.162: V/openxu(22630): mTextColor:-16777216
05-21 02:56:49.162: V/openxu(22630): mTextSize:100
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6


  看看多麼舒服的結果,我們得到了想要的寬高(float型),背景顏色(color的十進制)等,TypedArray提供了一系列獲取不同類型屬性的方法,這樣就可以直接得到我們想要的數據類型,而不用像Attributeset獲取屬性後還要一個個處理才能得到具體的數據,實際上TypedArray是爲我們獲取屬性值提供了方便,注意一點,TypedArray使用完畢後記得調用 ta.recycle();回收 。


好了,今天的任務算是完成了,我們已經完全掌握自定義屬性,總結一下這篇博客的重點內容:

  1. 爲什麼要自定義屬性
  2. 自定義屬性的方法
  3. 聲明使用系統自帶的屬性
  4. 屬性值的類型format
  5. 構造方法中獲取屬性值
  6. Attributeset、TypedArray以及declare-styleable的用法


不知不絕天快亮了,各位學習的童鞋,鼓勵鼓勵,評論評論順便點個讚唄~謝過了喔



(function () { ('pre.prettyprint code').each(function () {
var lines = (this).text().split(\n).length;var numbering = $('
    ').addClass('pre-numbering').hide();
    (this).addClass(hasnumbering).parent().append( numbering);
    for (i = 1; i <= lines; i++) {
    numbering.append( ('
  • ').text(i));
    };
    $numbering.fadeIn(1700);
    });
    });

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章