零、序
一、自定義Style
二、在XML中為屬性聲明屬性值
1. 在layout中定義屬性
2. 設定Style
3. 通過Theme指定
三、在運作時擷取屬性值
1. View的第三個構造函數的第三個參數defStyle
2. obtailStyledAttributes
3. Example
四、結論與代碼下載下傳
系統自帶的View可以在xml中配置屬性,對于寫的好的Custom View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置,需要以下4個步驟:
-
- 通過<declare-styleable>為自定義View添加屬性
- 在xml中為相應的屬性聲明屬性值
- 在運作時(一般為構造函數)擷取屬性值
- 将擷取到的屬性值應用到View
怎麼将擷取到的屬性值應用到View就不用說了,自己定義的屬性什麼用處自己肯定是清楚的,是以接下來看一下前三點。
通過<declare-styleable>元素聲明Custom View需要的屬性即可,下面是一個例子,檔案是res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Customize">
<attr name="attr_one" format="string" />
<attr name="attr_two" format="string" />
<attr name="attr_three" format="string" />
<attr name="attr_four" format="string" />
</declare-styleable>
<attr name="CustomizeStyle" format="reference" />
</resources>
在上述xml中,我們聲明了Customize與CustomizeSyle,Customize包含了attr_one、attr_two、attr_three與attr_four四個attribute,CustomizeStyle也是一個attribute,但是卻沒有聲明在declare-styleable中。
定義在declare-styleable中與直接用attr定義沒有實質的不同,上述xml中,無論attr_one - attr_four是否聲明在declare-styleable中,系統都會為我們在R.attr中生成5個attribute
public static final class attr {
public static final int CustomizeStyle=0x7f010004;
public static final int attr_one=0x7f010000;
public static final int attr_two=0x7f010001;
public static final int attr_three=0x7f010002;
public static final int attr_four=0x7f010003;
}
不同的是,如果聲明在declare-styleable中,系統還會為我們在R.styleable中生成相關的屬性。
public static final class styleable {
public static final int[] Customize = {
0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003
};
public static final int Customize_attr_one = 0;
public static final int Customize_attr_two = 1;
public static final int Customize_attr_three = 2;
public static final int Customize_attr_four = 3;
}
如上所示,R.styleable.Customize是一個int[],而裡面的元素的值正好和R.attr.attr_one - R.attr.attr_four一一對應,而R.styleable.Customize_attr_one等4個值就是R.attr.attr_one-R.attr.attr_four在R.styleable.Customize數組中的索引。這個數組和索引在第三步運作時獲得屬性值時會用到,将attr分組聲明在declare-styleabe中的作用就是系統會自動為我們生成這些東西,如果不聲明在declare-styleable中,我們完全可以在需要的時候自己建構這個數組,由于數組是自己建構的,每個屬性的下标索引也就很清楚了,隻是比較麻煩。以上一家之言,不過從使用及實驗難上看确實是這樣的。
二、在xml中為相應的屬性聲明屬性值
我們知道,在xml中為屬性指派有幾種不同的方式
- 直接在layout中使用屬性
- 設定style并在style中設定屬性
- Application和Activity可以指定theme,可以在theme中指定在目前Application或Activity中屬性的預設值
下面就分别看一下這三種方式
1. 直接在layout中使用屬性
在xml layout中使用自定義屬性和使用系統屬性差不多,不過屬性所屬的namespace不同,比如像下面這樣。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ad="http://schemas.android.com/apk/res/com.angeldevil.customstyle"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.angeldevil.customstyle.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
ad:attr_one="attr one in xml"
style="@style/ThroughStyle"
android:text="@string/hello_world" />
</RelativeLayout>
像layout_width等屬性屬于android的namespace,自定義的屬性屬于目前程式的namespace,隻需像聲明android的namespace一樣聲明目前程式的namespace就好,隻需要把上面紅色部分的android換成目前程式的包名。應用屬性的時候也需要注意屬性的namespace。
2. 設定style并在style中設定屬性
看上面xml中綠色的那一行,我們為CustomTextView聲明了一個style:ThroughStyle,這個Style很簡單,隻是指定了兩個屬性的值
<style name="ThroughStyle">
<item name="attr_one">attr one from style</item>
<item name="attr_two">attr two from style</item>
</style>
注意,在style中我們聲明了attr_one的值,同時在xml中也直接向attr_one賦了值,最終用哪一個有個優先級的問題,後面在第三節:在運作時擷取屬性值中再說,接下來看下第三種方式。
3. theme中指定在目前Application或Activity中屬性的預設值
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
<item name="attr_one">attr one from theme</item>
<item name="attr_two">attr two from theme</item>
<item name="attr_three">attr three from theme</item>
<item name="CustomizeStyle">@style/CustomizeStyleInTheme</item>
</style>
<style name="CustomizeStyleInTheme">
<item name="attr_one">attr one from theme reference</item>
<item name="attr_two">attr two from theme reference</item>
<item name="attr_three">attr three from theme reference</item>
</style>
在上述xml中,我們在AppTheme中為attr_one、attr_two與attr_three指定了值,但是同時也為CustomizeStyle指定了一個reference,而在這個reference中為attr_one、attr_two與attr_three指定了值,CustomizeStyle就是我們在attrs.xml中定義的一個屬性。在theme中為屬性設定值的方式有兩這種,直接在theme中定義或通過另一個attribute引用一個style,這兩種方式還是有差別的,在下面的擷取屬性值一節中可以看到。
在styles.xml中,我們同時定義一個DefaultCustomizeStyle的style,它的作用等下可以看到。
<style name="DefaultCustomizeStyle">
<item name="attr_one">attr one from defalut style res</item>
<item name="attr_two">attr two from defalut style res</item>
<item name="attr_three">attr three from defalut style res</item>
</style>
先看下CustomTextView的代碼
public class CustomTextView extends TextView {
private static final String TAG = CustomTextView.class.getSimpleName();
public CustomTextView(Context context) {
super(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.CustomizeStyle);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyle, R.style.DefaultCustomizeStyle);
String one = a.getString(R.styleable.Customize_attr_one);
String two = a.getString(R.styleable.Customize_attr_two);
String three = a.getString(R.styleable.Customize_attr_three);
String four = a.getString(R.styleable.Customize_attr_four);
Log.i(TAG, "one:" + one);
Log.i(TAG, "two:" + two);
Log.i(TAG, "three:" + three);
Log.i(TAG, "four:" + four);
a.recycle();
}
}
主要代碼都在第三個函數中,這裡也沒做什麼,隻是通過Context的obtainStyledAttributes獲得了前面定義的4個屬性的值并列印了出來。
View的第三個構造函數的第三個參數defStyle
如果在Code中執行個體化一個View會調用第一個構造函數,如果在xml中定義會調用第二個構造函數,而第三個函數系統是不調用的,要由View(我們自定義的或系統預定義的View,如此處的CustomTextView和Button)顯式調用,比如在這裡我們在第二個構造函數中調用了第三個構造函數,并将R.attr.CustomizeStyle傳給了第三個參數。
第三個參數的意義就如同它的名字所說的,是預設的Style,隻是這裡沒有說清楚,這裡的預設的Style是指它在目前Application或Activity所用的Theme中的預設Style,以系統中的Button為例說明。
public Button(Context context) {
this(context, null);
}
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
public Button(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
在Code中執行個體化View會調用第一個構造函數,在XML中定義會調用第二個構造函數,在Button的實作中都調用了第三個構造函數,并且defStyle的值是com.android.internal.R.attr.buttonStyle。buttonStyle是系統中定義的一個attribute,系統預設有一個Theme,比如4.0中是Theme.Holo
<style name="Theme.DeviceDefault" parent="Theme.Holo" >
...
<item name="buttonStyle">@android:style/Widget.DeviceDefault.Button</item>
....
</style>
上面是系統預設的Theme,為buttonStyle指定了一個Style
<style name="Widget.DeviceDefault.Button" parent="Widget.Holo.Button" >
</style>
<style name="Widget.Holo.Button" parent="Widget.Button">
<item name="android:background">@android:drawable/btn_default_holo_dark</item>
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
<item name="android:textColor">@android:color/primary_text_holo_dark</item>
<item name="android:minHeight">48dip</item>
<item name="android:minWidth">64dip</item>
</style>
這個Style定義了系統中Button的預設屬性,如background等。
從文檔中第三個構造函數的說明中也可以看到,這個構造函數的作用是View的子類提供這個類的基礎樣式
View(Context context, AttributeSet attrs, int defStyleAttr)
Perform inflation from XML and apply a class-specific base style.
上面說的都比較抽象,還是直接看執行個體代碼來的清楚明白,實驗用的代碼上面全都貼完了,這裡直接看結果,但在這之前要先看一個函數:obtainStyledAtributes。
obtainStyledAtributes
我們要擷取的屬性值都是通過這個函數傳回的TypedArray獲得的,這是官方說明:obtainStyledAttributes,以下是函數原型:
public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
4個參數的意思分别是:
set:屬性值的集合
attrs:我們要擷取的屬性的資源ID的一個數組,如同ContextProvider中請求資料庫時的Projection數組,就是從一堆屬性中我們希望查詢什麼屬性的值
defStyleAttr:這個是目前Theme中的一個attribute,是指向style的一個引用,當在layout xml中和style中都沒有為View指定屬性時,會從Theme中這個attribute指向的Style中查找相應的屬性值,這就是defStyle的意思,如果沒有指定屬性值,就用這個值,是以是預設值,但這個attribute要在Theme中指定,且是指向一個Style的引用,如果這個參數傳入0表示不向Theme中搜尋預設值
defStyleRes:這個也是指向一個Style的資源ID,但是僅在defStyleAttr為0或defStyleAttr不為0但Theme中沒有為defStyleAttr屬性指派時起作用
連結中對這個函數說明勉強過得去,這裡簡要概括一下。對于一個屬性可以在多個地方指定它的值,如XML直接定義,style,Theme,而這些位置定義的值有一個優先級,按優先級從高到低依次是:
直接在XML中定義>style定義>由defStyleAttr和defStyleRes指定的預設值>直接在Theme中指定的值
看這個關系式就比較明白了,defStyleAttr和defStyleRes在前面的參數說明中已經說了,“直接在Theme中指定的值”的意思在下面的示代碼中可以看到。
實驗驗證
相關的代碼都前面都已經貼上了,不過為了友善檢視,這裡再把相關的XML一起貼一遍
main_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ad="http://schemas.android.com/apk/res/com.angeldevil.customstyle"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.angeldevil.customstyle.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
ad:attr_one="attr one in xml"
style="@style/ThroughStyle"
android:text="@string/hello_world" />
</RelativeLayout>
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Customize">
<attr name="attr_one" format="string" />
<attr name="attr_two" format="string" />
<attr name="attr_three" format="string" />
<attr name="attr_four" format="string" />
</declare-styleable>
<attr name="CustomizeStyle" format="reference" />
</resources>
styles.xml
<resources>
<style name="AppBaseTheme" parent="android:Theme.Light">
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
<item name="attr_one">attr one from theme</item>
<item name="attr_two">attr two from theme</item>
<item name="attr_three">attr three from theme</item>
<item name="CustomizeStyle">@style/CustomizeStyleInTheme</item>
</style>
<style name="CustomizeStyleInTheme">
<item name="attr_one">attr one from theme reference</item>
<item name="attr_two">attr two from theme reference</item>
<item name="attr_three">attr three from theme reference</item>
</style>
<style name="ThroughStyle">
<item name="attr_one">attr one from style</item>
<item name="attr_two">attr two from style</item>
</style>
<style name="DefaultCustomizeStyle">
<item name="attr_one">attr one from defalut style res</item>
<item name="attr_two">attr two from defalut style res</item>
<item name="attr_three">attr three from defalut style res</item>
</style>
</resources>
在Code中是這樣擷取屬性的
public CustomTextView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.CustomizeStyle);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyle,
R.style.DefaultCustomizeStyle);
...
}
CustomizeStyle是定義的一個attribute,DefaultCustomizeStyle是定義的一個style。
從代碼中可以看到,R.attr.CustomizeStyle就是前面提到的defStyleAttr,R.style.DefaultCustomizeStyle就是defStyleRes。
在Styles.xml中我們為App定義了一個Theme:AppTheme,在這個Theme中直接為attr_one、attr_two、attr_three定義了屬性值,這個就是前面關系式中說的直接在Theme中指定的值。
在AppTheme中同時定義了CustomizeStyle,引用了一個Style,在CustomTextView的構造函數中分别列印了attr_one、attr_two、attr_three、attr_four的值,是以下面我們就可以通過Log輸出驗證一下前面的關系式,如果一個attribute在多個位置都定義了值,究竟哪一個起作用。

如上圖所示:
-
- attr_one同時在xml、style、defStyleAttr、defStyleRes與Theme中直接定義了值,但起作用的是在xml中的定義
- attr_two同時在style、defStyleAttr、defStyleRes與Theme中直接定義了值,但起用的是在style中的定義
- attr_three同時在defStyleAttr、defStyleRes與Theme中直接定義了值,但起作用的是defStyleAttr
- attr_four在defStyleRes中定義了,但結果仍是0。
這可以說明:
1. 直接在XML中定義>style定義>由defStyleAttr定義的值>defStyleRes指定的預設值、直接在Theme中指定的值
2. defStyleAttr(即defStyle)不為0且在目前Theme中可以找到這個attribute的定義時,defStyleRes不起作用,是以attr_four雖然在defStyleRes(DefaultCustomizeStyle)中定義了,但取到的值仍為null。
為了看一下defStyleRes的作用,1. 我們在AppTheme中加上attr_four的定義,2. 向obtainStyledAttributes的第三個參數傳入0,或者移除AppTheme中CustomizeStyle的定義,結果是一樣的
<style name="AppTheme" parent="AppBaseTheme">
<item name="attr_one">attr one from theme</item>
<item name="attr_two">attr two from theme</item>
<item name="attr_three">attr three from theme</item>
<item name="attr_four">attr four from theme</item>
</style>
由于defStyleAttr為0,或者defStyleAttr不為0但是我們沒有為這個屬性指派,是以defStyleRes起作用,當一個屬性沒有在XML和Style中指派時,系統會在defStyleRes(此處為DefaultCustomizeStyle)查找屬性的值。
我們看到attr_three的值來自defStyleRes,而attr_four的值來自Theme中的直接定義(DefaultCustomizeStyle定義了attr_one、atrr_two、attr_three) ,這就說明
1. defStyleAtrtr即defStyle為0或Theme中沒有定義defStyle時defStyleRes才起作用
2. defStyleRes>在Theme中直接定義
結論
從前面的說明可以得到以下結論:
-
- 要為自定義View自定義屬性,可以通過declare-styleable聲明需要的屬性
- 為了在Theme設定View的預設樣式,可以同時定義一個format為reference的屬性att_a,在定義Theme時為這個attribute指定一個Style,并且在View的第二個構造函數中将R.attr.attr_a 作為第三個參數調用第三個構造函數
- 可以定義一個Style作為Theme中沒有定義attr_a時View屬性的預設值。
- 可以在Theme中直接為屬性指派,但優先級最低
- 當defStyleAttr(即View的構造函數的第三個參數)不為0且在Theme中有為這個attr指派時,defStyleRes(通過obtainStyledAttributes的第四個參數指定)不起作用
- 屬性值定義的優先級:xml>style>Theme中的預設Sytle>預設Style(通過obtainStyledAttributes的第四個參數指定)>在Theme中直接指定屬性值
代碼下載下傳:CustomStyle.zip
作者:AngelDevil
出處:www.cnblogs.com/angeldevil
轉載請注明出處!