Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View(二、深入解析自定義屬性)

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

編輯:關於Android編程

這篇博客我們看一看自定義屬性到底是怎麼一個流程,為什麼要這樣做。

1. 為什麼要自定義屬性

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

<code class="language-xml hljs "><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">
     ...
</attr></attr></attr></attr></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">
     ...
</attr></attr></attr></attr></attr></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">
    </enum></enum></enum></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">
    </enum></enum></enum></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">
</attr></attr></attr></attr></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">
     ...
</attr></attr></attr></declare-styleable></code>


??看看上面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的屬性(屬性名稱可以隨便起的)

<code class="language-xml hljs "><resources>
    <declare-styleable name="MyTextView">
        <attr name="“text"" format="string">
    </attr></declare-styleable>
</resources></code>

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


    
        
    

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

 

3. 屬性值的類型format

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

屬性定義:

     
屬性使用:

(2). color:顏色值

屬性定義:
屬性使用:

(3). boolean:布爾值

屬性定義:
屬性使用:

(4). dimension:尺寸值

屬性定義:
屬性使用:

(5). float:浮點值

屬性定義:
屬性使用:

(6). integer:整型值

屬性定義:
屬性使用:

(7). string:字符串

屬性定義:
屬性使用:

(8). fraction:百分數

屬性定義:
屬性使用:

(9). enum:枚舉值

屬性定義:

    
        
        
    
屬性使用:

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

(10). flag:位或運算

屬性定義:

    
            
            
            
            
            
            ...
    
屬性使用:

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

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

屬性定義:

     
屬性使用:

或者:

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

 

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,定義自己的屬性:



    
        
        
        
        
    

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


        


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

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);
    Log.v("openxu", “text屬性值:"+mText);
    Log.v("openxu", "mTextColor屬性值:"+mTextColor);
    Log.v("openxu", "mTextSize屬性值:"+mTextSize);
}

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

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

屬性的定義我們應該學的差不多了,但有沒有發現構造方法中獲取屬性值的時候有兩個比較陌生的類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);
    }
}

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

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

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



        
        

之前的形式是這樣的:



    
        
        
        
        
        
        
    


或者:


          
       
        
    
        
        
        
        
        
        
        
    


??我們都知道所有的資源文件在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;
}


使用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);

    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);
}

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


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


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

為什麼要自定義屬性 自定義屬性的方法 聲明使用系統自帶的屬性 屬性值的類型format 構造方法中獲取屬性值 Attributeset、TypedArray以及declare-styleable的用法
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved