Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中View自定義XML屬性詳解以及R.attr與R.styleable的區別

Android中View自定義XML屬性詳解以及R.attr與R.styleable的區別

編輯:關於Android編程

為View添加自定義XML屬性

Android中的各種Widget都提供了很多XML屬性,我們可以利用這些XML屬性在layout文件中為Widget的屬性賦值。

如下所示:

我們可以通過TextView所提供的XML屬性android:text為TextView的文本賦值。

我們在自定義View的時候也會經常需要自定義View的XML屬性。

假設我們有一個自定義的View,其類名是com.ispring.attr.MyView,其中com.ispring.attr是應用程序的包名。

要想自定義XML屬性,總的來說包括三步:

在xml資源文件中定義各種attr,指定attr的數據類型。 在自定義View的構造函數中解析這些從XML中定義的屬性值,將其存放到View對應的成員變量中。 在layout文件中為自定義View的XML屬性賦值。

首先,我們在res/values目錄下新建了一個名為attrs_my_views.xml文件,文件名是什麼不重要,只要是xml文件就行。我們在該文件中定義MyView所支持的XML屬性。該文件的根結點是,我們在節點下可以添加多個節點,在節點中通過name指定XML屬性名稱,通過format指定XML屬性值的類型,如下圖所示:
這裡寫圖片描述

由上圖我們可知,format支持的類型有enum、boolean、color、dimension、flag、float、fraction、integer、reference、string。

當我們指定了XML屬性的名稱和屬性值的類型之後,我們就可以在layout文件中通過XML屬性為其賦值了。如下圖所示:
這裡寫圖片描述

我們通過在layout中引入了MyView,為了能夠使用自定義屬性,我們通常要指定一個自定義的命名空間以區別於Android的命名空間xmlns:android,我們自定義命名空間的名字可以是任意的,通常我一般用xmlns:app。我的App的命名空間是com.ispring.attr,如果用Eclipse開發,那麼可以這樣定義命名空間xmlns:app=http:/喎?/kf/ware/vc/" target="_blank" class="keylink">vc2NoZW1hcy5hbmRyb2lkLmNvbS9hcGsvcmVzL2NvbS5pc3ByaW5nLmF0dHI8L2NvZGU+o6y1q8rH1NpBbmRyb2lkIFN0dWRpb9bQ1eLR+bao0uXD/MP7v9W85Lvh09DOyszioaNBbmRyb2lkIFN0dWRpb8q508NHcmFkbGW9+NDQYnVpbGSjrLb4R3JhZGxlsrvUytDt19S2qNLltcTD/MP7v9W85NLUsPzD+73hzrKjrNTaQW5kcm9pZCBTdHVkaW/W0L/J0tTV4tH5tqjS5cP8w/u/1bzkPGNvZGU+eG1sbnM6YXBwPSZxdW90O2h0dHA6Ly9zY2hlbWFzLmFuZHJvaWQuY29tL2Fway9yZXMtYXV0byZxdW90OzwvY29kZT6jrNXi0fm2qNLltcTD/MP7v9W85NfUtq/WuM/ytbHHsEFwcLXEw/zD+7/VvOShozwvcD4NCjxwPtTa1f3It7ao0uVhcHC1xMP8w/u/1bzk1q6686OsztLDx77Nv8nS1NPDPGNvZGU+YXBwOmN1c3RvbUF0dHI8L2NvZGU+zqpNeVZpZXe1xGN1c3RvbUF0dHLK9NDUuLPWtcHLoaPI57n7ztLDx72rY3VzdG9tQXR0crXEZm9ybWF0tqjS5c6qYm9vbGVhbrXEo6zEx8O0tMu0pr7N1rvE3Mzu0LR0cnVlu/LV32ZhbHNlo6zM7tC0xuTL+8Dg0M21xNa1u+GxqLTtoaM8L3A+DQo8cD7PwsPm1Nm21GF0dHK1xGZvcm1hdLXEwODQzb340NDSu8/Cy7XD96GjPC9wPg0KPHA+Ym9vbGVhbjxiciAvPg0KYm9vbGVhbrHtyr5hdHRyyseyvLb7wODQzbXE1rWjrMih1rXWu8Tcysd0cnVlu/JmYWxzZaGjPC9wPg0KPHA+c3RyaW5nPGJyIC8+DQpzdHJpbmex7cq+YXR0csrH19a3+7SuwODQzaGjPC9wPg0KPHA+aW50ZWdlcjxiciAvPg0KaW50ZWdlcrHtyr5hdHRyysfV+8r9wODQzaOsyKHWtda7xNzKx9X7yv2jrLK7xNzKx7ihtePK/aGjPC9wPg0KPHA+ZmxvYXQ8YnIgLz4NCmZsb2F0se3KvmF0dHLKx7ihtePK/cDg0M2jrMih1rXWu8Tcyse4obXjyv278tX7yv2hozwvcD4NCjxwPmZyYWN0aW9uPGJyIC8+DQpmcmFjdGlvbrHtyr5hdHRyysew2bfWyv3A4NDNo6zIoda11rvE3NLUJb3hzrKjrMD9yOczMCWhojEyMC41JbXIoaM8L3A+DQo8cD5jb2xvcjxiciAvPg0KY29sb3Kx7cq+YXR0csrH0dXJq8Dg0M2jrMD9yOcjZmYwMDAwo6zSsr/J0tTKudPD0ru49ta4z/JDb2xvcrXE18rUtKOsscjI50BhbmRyb2lkOmNvbG9yL2JhY2tncm91bmRfZGFya6OstavKx7K7xNzTwzB4ZmZmZjAwMDDV4tH5tcTWtaGjPC9wPg0KPHA+ZGltZW5zaW9uPGJyIC8+DQpkaW1lbnNpb26x7cq+YXR0csrHs9+058Dg0M2jrMD9yOfIoda1MTZweKGiMTZkcKOs0rK/ydLUyrnTw9K7uPbWuM/yPGNvZGU+PGRpbWVuPjwvZGltZW4+PC9jb2RlPsDg0M21xNfK1LSjrLHIyOc8Y29kZT5AYW5kcm9pZDpkaW1lbi9hcHBfaWNvbl9zaXplPC9jb2RlPqGjPC9wPg0KPHA+cmVmZXJlbmNlPGJyIC8+DQpyZWZlcmVuY2Wx7cq+YXR0crXE1rXWu8Tc1rjP8sSz0rvXytS0tcRJRKOswP3I58ih1rU8Y29kZT5AaWQvdGV4dFZpZXc8L2NvZGU+oaM8L3A+DQo8cD5lbnVtPGJyIC8+DQplbnVtse3KvmF0dHLKx8O2vtnA4NDNo6zU2rao0uVlbnVtwODQzbXEYXR0csqxo6y/ydLUvathdHRytcRmb3JtYXTJ6NbDzqplbnVto6zSsr/J0tSyu9PDyejWw2F0dHK1xGZvcm1hdMr00NSjrLWrysex2NDr1NphdHRyvdq148/Cw+bM7bzT0ru49rvytuC49mVudW292rXjoaPI58/Cy/nKvqO6PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> <code class=" hljs cs"><attr name="customAttr"> <enum name="man" value="0"> <enum name="woman" value="1"> </enum></enum></attr></code>

這樣attr的屬性值只能取man或woman了。

flag
flag表示attr是bit位標記,flag與enum有相似之處,定義了flag的attr,在設置值時,可以通過|設置多個值,而且每個值都對應一個bit位,這樣通過按位或操作符|可以將多個值合成一個值,我們一般在用flag表示某個字段支持多個特性,需要注意的是,要想使用flag類型,不能在attr上設置format為flag,不要設置attr的format的屬性,直接在attr節點下面添加flag節點即可。如下所示:


    
    
    
    

節點下通過定義多個表示其支持的值,value的值一般是0或者是2的N次方(N為大於等於0的整數),對於上面的例子我們在實際設置值是可以設置單獨的值,如none、bold、italic、underline,也可以通過|設置多個值,例如app:customAttr="italic|underline"

MyView直接繼承自View,我想讓MyView可以顯示文本,即我傳遞文本給MyView,MyView能畫出來,就相當於非常簡單的TextView。

因此,我的attrs_my_view.xml如下所示:

<code class=" hljs xml"><resources>
    <attr name="customText" format="string">
    <attr name="customColor" format="color">
</attr></attr></resources></code>

我們定義了兩個XML屬性,customText是一個string類型,表示MyView要顯示的文本,customColor是color類型,表示文本的顏色。

對項目進行編譯之後會生成R.java文件,R.java文件對應著R類。如果是Android Studio項目,那麼R文件的目錄是app\build\generated\source\r\debug\com\ispring\attr\R.java,在該文件中有內部類public static final class attr,在R.attr中會發現有customTextcustomColor,如下圖所示:

這裡寫圖片描述

R.attr.customTextR.attr.customColor分別是屬性customTextcustomColor的資源ID。

在使用MyView時,我們可以在layout文件中為MyView設置customText和customColor兩個XML屬性。layout文件如下所示:

<code class=" hljs xml"><!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E-->
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.ispring.attr.MainActivity">

    <com.ispring.attr.myview android:layout_width="match_parent" android:layout_height="match_parent" app:customtext="Hello World!" app:customcolor="#FF0000FF">

</com.ispring.attr.myview></relativelayout></code>

運行效果如下所示:
這裡寫圖片描述

可以看出在界面上顯示了藍色的“Hello World!”文本,說明MyView的自定義屬性起作用了。

我們看一下MyView的具體實現,MyView的代碼如下所示:

package com.ispring.attr;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
    //存儲要顯示的文本
    private String mCustomText;
    //存儲文本的顯示顏色
    private int mCustomColor = 0xFF000000;
    //畫筆
    private TextPaint mTextPaint;
    //字體大小
    private float fontSize = getResources().getDimension(R.dimen.fontSize);

    public MyView(Context context) {
        super(context);
        init(null, 0);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        //首先判斷attrs是否為null
        if(attrs != null){
            //獲取AttributeSet中所有的XML屬性的數量
            int count = attrs.getAttributeCount();
            //遍歷AttributeSet中的XML屬性
            for(int i = 0; i < count; i++){
                //獲取attr的資源ID
                int attrResId = attrs.getAttributeNameResource(i);
                switch (attrResId){
                    case R.attr.customText:
                        //customText屬性
                        mCustomText = attrs.getAttributeValue(i);
                        break;
                    case R.attr.customColor:
                        //customColor屬性
                        //如果讀取不到對應的顏色值,那麼就用黑色作為默認顏色
                        mCustomColor = attrs.getAttributeIntValue(i, 0xFF000000);
                        break;
                }
            }
        }

        //初始化畫筆
        mTextPaint = new TextPaint();
        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(fontSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if(mCustomText != null && !mCustomText.equals("")){
            mTextPaint.setColor(mCustomColor);
            //將文本繪制顯示出來
            canvas.drawText(mCustomText, 0, fontSize, mTextPaint);
        }
    }
}

我們在MyView中定義了兩個成員變量mCustomText和mCustomColor。MyView的幾個構造函數都會調用init方法,我們重點看一下init方法。

傳遞給init方法的是一個AttributeSet對象,可以把它看成一個索引數組,這個數組裡面存儲著屬性的索引,通過索引可以得到XML屬性名和屬性值。

通過調用AttributeSet的getAttributeCount()方法可以獲得XML屬性的數量,然後我們就可以在for循環中通過索引遍歷AttributeSet的屬性名和屬性值。AttributeSet中有很多getXXX方法,一般必須的參數都是索引號,說幾個常用的方法:

通過AttributeSet的public abstract String getAttributeName (int index)方法可以得到對應索引的XML屬性名。

通過AttributeSet的public abstract int getAttributeNameResource (int index)方法可以得到對應索引的XML屬性在R.attr中的資源ID,例如R.attr.customText、R.attr.customColor。

如果index對應的XML屬性的format是string,那麼通過AttributeSet的public abstract String getAttributeName (int index)方法,可以得到對應索引的XML屬性的值,該方法返回的是String。除此之外,AttributeSet還有getAttributeIntValue、getAttributeFloatValue、getAttributeListValue等方法,返回不同類型的屬性值。

我們通過attrs.getAttributeNameResource(i)得到獲取attr的資源ID,然後對attrResId進行switch判斷:

如果是R.attr.customText,表示當前屬性是customText,我們通過attrs.getAttributeValue(i)讀取customText屬性值,並將其賦值給成員變量mCustomText。

如果是R.attr.customColor,表示當前屬性是customColor,由於Android中用一個4字節的int型整數表示顏色,所以我們通過attrs.getAttributeIntValue(i, 0xFF000000)讀取了customColor的顏色值,並將其賦值給成員變量mCustomColor。

我們重寫了MyView的onDraw方法,通過執行mTextPaint.setColor(mCustomColor)把畫筆設置成mCustomColor的顏色,通過執行canvas.drawText(mCustomText, 0, fontSize, mTextPaint)將mCustomText繪制到界面上。這樣,MyView就使用了customText和customColor這兩個XML屬性。


使用和obtainStyledAttributes方法

我們上面定義的customText和customColor這兩個屬性都是直接在節點下定義的,這樣定義屬性存在一個問題:不能想通過style或theme設置這兩個屬性的值。

要想能夠通過style或theme設置XML屬性的值,需要在節點下添加節點,並在節點下定義,如下所示:


    
        
        
    

需要給設置name屬性,一般name設置為自定義View的名字,我們此處設置為MyView。

下面定義的屬性與直接在定義的屬性其實本質上沒有太大區別,無論哪種方式定義,都會在R.attr類中定義R.attr.customText和R.attr.customColor。不同的是,節點會在R.styleable這個內部類中有如下定義:
這裡寫圖片描述

R.styleable.MyView是一個int數組,其值為0x7f010038和 0x7f010039。0x7f010038就是屬性R.attr.customText,0x7f010039就是屬性R.attr.customColor。也就是R.styleable.MyView等價於數組[R.attr.customText, R.attr.customColor]。R.styleable.MyView的作用會在下面介紹。

我們同樣可以在R.styleable中發現R.styleable.MyView_customColor和R.styleable.MyView_customText這兩個ID。中的name加上裡面的屬性的name就組成了R.styleable中的MyView_customColor和MyView_customText,中間以下劃線連接。如下圖所示:

這裡寫圖片描述

其中R.styleable.MyView_customColor對應R.attr.customColor,R.styleable.MyView_customText對應R.attr.customText。MyView_customColor和MyView_customText的作用在下面介紹。

中定義的在MyView中需要通過調用theme的obtainStyledAttributes方法來讀取解析屬性值。obtainStyledAttributes有三個重載方法,簽名分別如下所示:

public TypedArray obtainStyledAttributes (int[] attrs)

public TypedArray obtainStyledAttributes (int resid, int[] attrs)

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)

我們先看第三個最復雜的方法,在MyView中使用方法如下所示:

private void init(AttributeSet attributeSet, int defStyle) {
        //首先判斷attributeSet是否為null
        if(attributeSet != null){
            //獲取當前MyView所在的Activity的theme
            Resources.Theme theme = getContext().getTheme();
            //通過theme的obtainStyledAttributes方法獲取TypedArray對象
            TypedArray typedArray = theme.obtainStyledAttributes(attributeSet, R.styleable.MyView, 0, 0);
            //獲取typedArray的長度
            int count = typedArray.getIndexCount();
            //通過for循環遍歷typedArray
            for(int i = 0; i < count; i++){
                //通過typedArray的getIndex方法獲取指向R.styleable中對應的屬性ID
                int styledAttr = typedArray.getIndex(i);
                switch (styledAttr){
                    case R.styleable.MyView_customText:
                        //如果是R.styleable.MyView_customText,表示屬性是customText
                        //通過typedArray的getString方法獲取字符串值
                        mCustomText = typedArray.getString(i);
                        break;
                    case R.styleable.MyView_customColor:
                        //如果是R.styleable.MyView_customColor,表示屬性是customColor
                        //通過typedArray的getColor方法獲取整數類型的顏色值
                        mCustomColor = typedArray.getColor(i, 0xFF000000);
                        break;
                }
            }
            //在使用完typedArray之後,要調用recycle方法回收資源
            typedArray.recycle();
        }

        ...
    }

我們在res/valeus/styles.xml文件中定義了如下style:

然後我們在layout文件中將MyView的style屬性設置為上面的style,如下所示:

運行效果如下所示:
這裡寫圖片描述

我們雖然沒有直接設置MyView的customText和customColor兩個屬性,但是通過設置style屬性之後,在效果上RedStyle中所定義的屬性值應用到了MyView上了。

下面我們就來解釋一下init方法:

首先我們通過getContext().getTheme()獲取到了當前MyView所在的Activity的theme。

然後調用方法theme.obtainStyledAttributes(attributeSet, R.styleable.MyView, 0, 0),該方法返回一個TypedArray對象。TypedArray是一個數組,通過該數組可以獲取應用了style和theme的XML屬性值。上面這個方法有四個參數,後面兩個參數都是0,大家暫且忽略不計,後面會介紹。第一個參數還是AttributeSet對象,第二個參數是一個int類型的數組,該數組表示想要獲取的屬性值的屬性的R.attr中的ID,此處我們傳入的是R.styleable.MyView,在上面我們已經提到其值等價於[R.attr.customText, R.attr.customColor],表示我們此處想獲取customText和customColor這兩個屬性的值。

如果在layout文件中直接為MyView設置了某些XML屬性,那麼這些XML屬性及其屬性值就會出現在AttributeSet中,那麼Android就會直接使用AttributeSet中該XML屬性值作為theme.obtainStyledAttributes()的返回值,比如在上面的例子中,我們通過app:customText="customText in AttributeSet"設置了MyView的XML屬性,最終運行的效果顯示的也是文本”customText in AttributeSet”。

如果在layout文件中沒有為MyView設置某個XML屬性,但是給MyView設置了style屬性,例如style="@style/RedStyle",並且在style中指定了相應的XML屬性,那麼Android就會用style屬性所對應的style資源中的XML屬性值作為theme.obtainStyledAttributes()的返回值。比如在上面的例子中,我們在layout文件中沒有設置app:customColor的值,但是在其style屬性所對應的RedStyle資源中將customColor設置成了紅色#FFFF0000,最終文本也是以紅色顯示在界面上的。

通過以上描述,我們可以知道,View的style屬性對應的style資源中定義的XML屬性值其實是View直接在layou文件中定義XML屬性值的替補值,是用於補漏的,AttributeSet(即在layout中直接定義XML屬性)的優先級高於style屬性中資源所定義的屬性值。


obtainStyledAttributes方法之defStyleAttr

我們再看一下方法obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)中的第三個參數defStyleAttr,這個參數表示的是一個

我們添加了HelloWorldStyle和GreenStyle,其中HelloWorldStyle只定義了customText的屬性值,而GreenStyle同時定義了customText和customColor的值,其中customColor定義為綠色。

在AppTheme中,我們設置了myViewStyle這個屬性的值,如下所示:

@style/GreenStyle

myViewStyle這個屬性是在values/attrs_my_view.xml中定義的,如下所示:


    
    
        
        
    

myViewStyle被定義為一個reference格式,即其值指向一個資源類型,我們在AppTheme中將其賦值為@style/GreenStyle,即在AppTheme中,myViewStyle的就是GreenStyle,其指向了一個style資源。

我們更改MyView代碼,如下所示:

//通過theme的obtainStyledAttributes方法獲取TypedArray對象
            TypedArray typedArray = theme.obtainStyledAttributes(attributeSet, R.styleable.MyView, R.attr.myViewStyle, 0);

注意,上面obtainStyledAttributes方法最後一個參數還是為0,可以忽略,但是第三個參數的值不再是0,而是R.attr.myViewStyle。

然後我們更新layout文件,如下所示:

我們為MyView設置了style屬性,其值為HelloWorldStyle。

程序運行效果如下所示:
這裡寫圖片描述

我們發現結果是綠色的“Hello World!”,我們解釋一下原因。

由於這次我們沒有通過layout文件直接設置MyView的XML屬性的值,所以AttributeSet本身是沒有XML屬性值的,我們直接忽略掉AttributeSet。

我們通過style="@style/HelloWorldStyle"為MyView設置了style為HelloWorldStyle,HelloWorldStyle中定義customText的屬性值為”Hello World!”,所以最終customText的值就是”Hello World!”,在界面上顯示的也是該值。

HelloWorldStyle中並沒有定義customColor屬性值。我們將theme.obtainStyledAttributes()方法的第三個參數設置為R.attr.myViewStyle,此處的theme就是我們上面提到的AppTheme,Android會去AppTheme中查找屬性為myViewStyle的值,我們之前提到了,它的值就是@style/GreenStyle,即GreenStyle,由於該值是個style資源,Android就會去該資源中查找customColor的值,GreenStyle定義了customColor的顏色為綠色,所以MyView最終所使用的customColor的值就是綠色。

綜上,我們發現,此處的第三個參數的作用是:當在AttributeSet和style屬性中都沒有找到屬性值時,就去Theme的某個屬性(即第三個參數)中查看其值是否是style資源,如果是style資源再去這個style資源中查找XML屬性值作為替補值。


obtainStyledAttributes方法之defStyleRes

最後我們看一下方法obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)中的第四個參數defStyleRes。與defStyleAttr類似,defStyleRes是前面幾項的替補值,defStyleRes的優先級最低。與defStyleAttr不同的是,defStyleRes本身直接表示一個style資源,而theme要通過屬性defStyleAttr間接找到style資源。

我們添加了BlueStyle這個style,如下所示:

並將layout文件改為如下所示:

我們更改MyView代碼如下所示:

TypedArray typedArray = theme.obtainStyledAttributes(attributeSet, R.styleable.MyView, 0, R.style.BlueStyle);

第三個參數設置為0,第四個參數不再是0,而是R.style.BlueStyle。運行界面如下所示:
這裡寫圖片描述

只有第三個參數defStyleAttr為0或者該屬性在theme中找不到時,才會使用第四個參數defStyleRes。如果第三個參數defStyleAttr不為0,但是theme的defStyleAttr所對應的屬性值中的style沒有定義任何XML屬性值,那麼第四個參數也不會defStyleRes被使用。


總結

可以不通過節點定義XML屬性,不過還是建議將XML屬性定義在節點下,因為這樣Android會在R.styleable下面幫我們生成很多有用的常量供我們直接使用。

obtainStyledAttributes方法中,優先級從高到低依次是:直接在layout中設置View的XML屬性值(AttributeSet) > 設置View的style屬性 > defStyleAttr > defStyleRes

希望本文對大家理解與使用自定義XML屬性有所幫助!

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