Android中自定義樣式與View的構造函數中的第三個參數defStyle的意義

零、序html

1、自定義Styleandroid

2、在XML中爲屬性聲明屬性值數據庫

  1. 在layout中定義屬性數組

  2. 設置Styleapp

  3. 經過Theme指定ide

3、在運行時獲取屬性值函數

  1. View的第三個構造函數的第三個參數defStylethis

  2. obtailStyledAttributesspa

  3. Examplecode

4、結論與代碼下載

零、序

  系統自帶的View能夠在xml中配置屬性,對於寫的好的Custom View一樣能夠在xml中配置屬性,爲了使自定義的View的屬性能夠在xml中配置,須要如下4個步驟:

    1. 經過<declare-styleable>爲自定義View添加屬性

    2. 在xml中爲相應的屬性聲明屬性值

    3. 在運行時(通常爲構造函數)獲取屬性值

    4. 將獲取到的屬性值應用到View

  怎麼將獲取到的屬性值應用到View就不用說了,本身定義的屬性什麼用處本身確定是清楚的,因此接下來看一下前三點。

1、自定義Style

  經過<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中,咱們徹底能夠在須要的時候本身構建這個數組,因爲數組是本身構建的,每一個屬性的下標索引也就很清楚了,只是比較麻煩。以上一家之言,不過從使用及實驗難上看確實是這樣的。

2、在xml中爲相應的屬性聲明屬性值

        咱們知道,在xml中爲屬性賦值有幾種不一樣的方式

  1. 直接在layout中使用屬性

  2. 設置style並在style中設置屬性

  3. 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,這兩種方式仍是有區別的,在下面的獲取屬性值一節中能夠看到。

3、在運行時獲取屬性值

        在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中直接定義

 結論

   從前面的說明能夠獲得如下結論:

    1. 要爲自定義View自定義屬性,能夠經過declare-styleable聲明須要的屬性
    2. 爲了在Theme設置View的默認樣式,能夠同時定義一個format爲reference的屬性att_a,在定義Theme時爲這個attribute指定一個Style,而且在View的第二個構造函數中將R.attr.attr_a 做爲第三個參數調用第三個構造函數
    3. 能夠定義一個Style做爲Theme中沒有定義attr_a時View屬性的默認值。
    4. 能夠在Theme中直接爲屬性賦值,但優先級最低
    5. 當defStyleAttr(即View的構造函數的第三個參數)不爲0且在Theme中有爲這個attr賦值時,defStyleRes(經過obtainStyledAttributes的第四個參數指定)不起做用
    6. 屬性值定義的優先級:xml>style>Theme中的默認Sytle>默認Style(經過obtainStyledAttributes的第四個參數指定)>在Theme中直接指定屬性值

  代碼下載:CustomStyle.zip

相關文章
相關標籤/搜索