1. 程式人生 > >Android obtainStyledAttributes獲取屬性值

Android obtainStyledAttributes獲取屬性值

obtainStyledAttributes是幹什麼的

有過自定義屬性或者檢視過系統View相關子類原始碼的人可能對這個方法都不會陌生。
該方法是Context類為我們提供的獲取style中特定屬性值的方法。通過這個方法,我們就可以獲取在style中定義的各種屬性值,然後根據獲取到的不同的屬性值實現差異化的效果。

一種典型的使用方式是:

        //TextView 構造方法片選程式碼

        //首先通過obtainStyledAttributes獲取TypedArray
        TypedArray a = theme.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 
        //接著從TypedArray中提取相應的值,此處提取的是resourceId,對於的attr宣告應為“reference”        
//除此之外,TypedArray還有getInt,getDrawable等一系列方法用於提取其他型別的值。 int ap = a.getResourceId( com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); //回收TypedArray a.recycle();

從以上程式碼片看到呼叫obtainStyledAttributes的變數是theme,其實是因為context中的該方法最終也是呼叫其持有的theme物件的該方法,因此是一致的。如下:

    //Context obtainStyledAttributes實現
    public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    }

既然如此,我們就直接分析Theme中的obtainStyledAttributes方法。

obtainStyledAttributes API解析

這裡寫圖片描述

以上是文件中對該方法的說明。我們先從引數部分看。該方法總共有四個引數:

引數名 說明
set 直接內嵌在View中的屬性集合,如: <Button textColor=”#ff000000”>。
同時還包括通過style=“@style/xxxx”引入的屬性,如:<Button style=”@style/xxxx”>
attrs 想要獲取的屬性集合,通常我們在宣告屬性時可以將之作為styleable的孩子,那麼此時就可以直接使用如TextView中的方式引用那個styleable資源就行,如:R.styleable.TextViewAppearance
但有時由只想獲取某一個或者幾個屬性的話,就應該手動構建int陣列了,如:new int[]{R.attr.xxx, ...}
defStyleAttr 指定預設引用的屬性,該屬性應是一個指向某個style的引用。注意註釋中的說明,該屬性的值是從theme中提取的,如果該屬性是包含在第一個引數“set”中的(使用內聯方式指定或者style嵌入方式指定到元素標籤內),那麼會獲取不到該值,因此只能夠在theme中定義該屬性的值
當theme中存在該屬性的定義時,系統會將該屬性指向的style內容引入進當前的內容作為屬性集提供檢索,而當不提供該引數時,系統是不會將Theme中那些指向style的屬性內容展開的。
defStyleRes 當defStyleAttr=0或者Theme中未定義defStyleAttr屬性時使用該處指向的style提供預設的屬性集,比起defStyleAttr,該引數因為指定確定的style而缺乏靈活性,但其保證了元件中某些必須要獲取到的屬性值不會不存在。



總的來說,這個方法就在從一堆屬性中提取自己需要的屬性。這一堆屬性的來源依據說明文件中的說法是有序的從以下地方提取的:

When determining the final value of a particular attribute, there are four inputs that come into play:

  1. Any attribute values in the given AttributeSet. ——引數set
  2. The style resource specified in the AttributeSet (named “style”). ——引數set
  3. The default style specified by defStyleAttr and defStyleRes. ——引數defStyleAttr和defStyleRes
  4. The base values in this theme. ——theme

而我們需要獲取的屬性就由int[]型的引數attrs指定。

這樣,弄清了系統元件是如何獲取屬性的,我們就可以有針對性的修改屬性來定義不同的效果了。比如修改預設的EditText獲得焦點是底部線條的顏色。

修改預設的EditText獲得焦點時底部線條的顏色

有時候我們只是想簡單的修改獲得焦點是EditText的顏色(比如使用紅色提示輸入格式不正確)而不想全部將EditText的background替換成自己寫的selector(背景選擇器),有沒有簡單的方法呢?當然有!

首先,我們檢視系統預設的EditText的背景是個什麼,我們才能有針對性的修改。
檢視EditText原始碼發現其構造方法中指定了預設style為com.android.internal.R.attr.editTextStyle,即為android:editTextStyle

    //在inflate xml佈局檔案過程中,一般使用該構造方法
    public EditText(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.editTextStyle);
    }

既然如此,那麼我們就看看我們使用的Theme中定義的android:editTextStyle屬性值是什麼吧。

SDK/platforms/android-25/data/res/values/themes.xml中發現如下:

<item name=”editTextStyle”>@style/Widget.EditText</item>

再檢視style/Widget.EditText,發現其中background屬性使用的是?attr/editTextBackground

    <style name="Widget.EditText">
        <item name="focusable">true</item>
        <item name="focusableInTouchMode">true</item>
        <item name="clickable">true</item>
        <item name="background">?attr/editTextBackground</item>
        <item name="textAppearance">?attr/textAppearanceMediumInverse</item>
        <item name="textColor">?attr/editTextColor</item>
        <item name="gravity">center_vertical</item>
        <item name="breakStrategy">simple</item>
        <item name="hyphenationFrequency">normal</item>
    </style>

於是折回theme中查詢editTextBackground,找到為:

<item name=”editTextBackground”>@drawable/edit_text_material</item>

那麼drawable/edit_text又是什麼呢,繼續……

<!--SDK/platforms/android-25/data/res/drawable/edit_text_material.xml-->

<inset xmlns:android="http://schemas.android.com/apk/res/android"
       android:insetLeft="@dimen/edit_text_inset_horizontal_material"
       android:insetRight="@dimen/edit_text_inset_horizontal_material"
       android:insetTop="@dimen/edit_text_inset_top_material"
       android:insetBottom="@dimen/edit_text_inset_bottom_material">
    <selector>
        <item android:state_enabled="false">
            <nine-patch android:src="@drawable/textfield_default_mtrl_alpha"
                android:tint="?attr/colorControlNormal" />
        </item>
        <item android:state_pressed="false" android:state_focused="false">
            <nine-patch android:src="@drawable/textfield_default_mtrl_alpha"
                android:tint="?attr/colorControlNormal" />
        </item>
        <item>
            <nine-patch android:src="@drawable/textfield_activated_mtrl_alpha"
                android:tint="?attr/colorControlActivated" />
        </item>
    </selector>
</inset>

可以看到其中預設狀態時(也是選擇啟用狀態)的背景設定為一個nine-patch並設定了tint(染色)屬性為?attr/colorControlActivated

於是乎,我們打可以使用android:colorControlActivated這個屬性來設定選擇時線條的顏色。因此在Theme中定義

<item name=”android:colorControlActivated”>#ff0000</item>

同理,可以設定未選中狀態的顏色通過定義colorControlNormal

後記

注意!!!
以上edit_text_material中tint的使用方式為引用系統屬性colorControlActivated,因此不能通過<EditText android:colorControlActivated="#ff0000">來實現,只能在Theme中指定該屬性!

那這就引出另一個問題。我們在Theme中修改該系統屬性值可能也在被其他元件使用。豈不是都會被影響到?確實是這樣的,但是這是有解決辦法的。可以使用ContextThemeWrapper來解決,使該屬性的定義隻影響某個或者某些元素。具體使用方式可以百度ContextThemeWrapper或參見本人部落格Android ContextThemeWrapper應用