標籤:
相信每一位從事Android開發的猿都遇到過需要自己去自訂View的需求,如果想通過xml指定一些我們自己需要的參數,就需要自己聲明一個styleable,並在裡面自己定義一些attr屬性,這個過程相信大家都比較瞭解。當然,屬性其實也不一定需要和View配合使用,比如我想通過一個Theme中的style對一個庫進行一些簡單參數的配置,這應該怎麼做呢?我今天在封裝一個庫時在這個地方浪費了較多時間,最後沒辦法,到處搜搜資料,記錄在這裡吧,相信對大家都有協助。
attr和styleable的關係
首先要明確一點,attr不依賴於styleable,styleable只是為了方便attr的使用。
我們自己定義屬性完全可以不放到styleable裡面,比如直接在resources檔案中定義一些屬性:
<attr name="custom_attr1" format="string" /><attr name="custom_attr2" format="string" />
定義一個attr就會在R檔案裡面產生一個Id,那麼我們去擷取這個屬性時,必須調用如下代碼:
int[] custom_attrs = {R.attr.custom_attr1,R.custom_attr2};TypedArray typedArray = context.obtainStyledAttributes(set,custom_attrs);
而通過定義一個styleable,我們可以在R檔案裡自動產生一個int[],數組裡面的int就是定義在styleable裡面的attr的id。所以我們在擷取屬性的時候就可以直接使用styleable數組來擷取一系列的屬性。
<declare-styleable name="custom_attrs"> <attr name="custom_attr1" format="string" /> <attr name="custom_attr2" format="string" /> </declare-styleable>
擷取:
TypedArray typedArray = context.obtainStyledAttributes(set,R.styleable.custom_attrs);
由上面的例子可以知道,定義一個declare-styleable,在擷取屬性的時候為我們自動提供了一個屬性數組。此外,我覺得使用declare-styleable的方式有利於我們我們把相關的屬性群組織起來,有一個分組的概念,屬性的使用範圍更加明確。
obtainStyledAttributes函數擷取屬性
其實我們在前面已經使用了obtainStyledAttributes來擷取屬性了,現在來看看這個函數的聲明吧:
- obtainAttributes(AttributeSet set, int[] attrs) //從layout設定的屬性集中擷取attrs中的屬性
- obtainStyledAttributes(int[] attrs) //從系統主題中擷取attrs中的屬性
- obtainStyledAttributes(int resId,int[] attrs) //從資源檔定義的style中讀取屬性
- obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
//這是最複雜的一種情況,後面細說。
這麼多重載的方法是不是已經看懵了?其實你只需要理解其中的參數就能掌握各個方法的使用方法。所謂擷取屬性,無非就是需要兩個參數:第一,我需要擷取那些屬性;第二:我從哪裡去擷取這些屬性(資料來源)。
attrs
attrs:int[],每個方法中都有的參數,就是告訴系統需要擷取那些屬性的值。
set
set:表示從layout檔案中直接為這個View添加的屬性的集合,如:android:layout_width="match_parent"。注意,這裡面的屬性必然是通過xml配置添加的,也就是由LayoutInflater載入進來的布局或者View`才有這個屬性集。
現在你知道為啥我們在自己定義View的時候至少要重寫(Context context, AttributeSet set)構造器了吧?因為不重寫時,我們將無法擷取到layout中配置的屬性!!當然,也因為這樣,LayoutInflater在inflater布局時會通過反射去調用View的(Context context, AttributeSet attrs)構造器。
set 中實際上又有兩種資料來源,當然最後都會包含在set中。一種是直接使用android:layout_width="wrap_content"這種直接指定的,還有一種是通過style="@style/somestyle"這樣指定的。
defStyleAttr
這個參數是本文的關鍵所在,也是自訂一個可以在Theme中配置的樣式的關鍵,先看個栗子吧:
如果我想通過在系統主題裡面設定一個樣式,修改所有textview的樣式,你一般會這麼做:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> //在主題中設定textview的style <item name="android:textViewStyle">@style/textviewstyle</item></style><style name="textviewstyle" parent="android:style/Widget.TextView"> <!--指定一些屬性--></style>
首先android:textViewStyle其實就是一個普通的在資源檔中定義的屬性attr,它的format="reference"。那問題來了,TextView是怎麼得知我們自己定義的textviewstyle的呢?這其實就是defStyleAttr的應用情境:定義Theme可配置樣式。
public TextView(Context context, AttributeSet attrs) { //指定屬性textViewStyle為defStyleAttr,然後系統會去搜尋Theme中你為這個 //屬性配置的style this(context, attrs, com.android.internal.R.attr.textViewStyle); } public TextView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); //最終調用到View的第四個構造器時,調用了obtainStyledAttributes TypedArray a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); }
resId=defStyleRes
resId or defStyleRes:直接從資源檔中定義的某個樣式中讀取。
NULL
看看第二個方法吧,裡面除了指定了attrs屬性集之外沒有任何屬性值來源,資料從哪兒來呢?原來我們可以直接在Theme中指定屬性的值,那麼NULL表示直接從Theme中讀取屬性。
是不是看到這裡你已經有點迷糊了?不要緊,耐心看下去,後面有一個例子,看完例子你再回頭看看這裡的說明就ok了。
四個參數的obtainStyledAttributes
看看這個方法,返回的結果還是我們所關心的attrs(int[])中包含的屬性集。那麼資料來源呢?一共有4個,set,defStyleAttr,NULL,defStyleRes,如果一個屬性在多個地方都被定義了,那麼以哪個為準?
優先順序如下:
set>defStyleAttr(主題可配置樣式)>defStyleRes(預設樣式)>NULL(主題中直接指定)
栗子終於來了!!-GitHub
attr資源檔中如下定義:
//定義屬性<declare-styleable name="custom_attrs"> <attr name="custom_color1" format="color"></attr> <attr name="custom_color2" format="color"></attr> <attr name="custom_color3" format="color"></attr> <attr name="custom_color4" format="color"></attr> <attr name="custom_color5" format="color"></attr></declare-styleable>//定義theme可配置style<attr name="custom_style" format="reference"></attr> //定義預設style<style name="default_style"> <item name="custom_color1">#ff333333</item> <item name="custom_color2">#ff333333</item> <item name="custom_color3">#ff333333</item> <item name="custom_color4">#ff333333</item></style>
styles資源檔中如下定義:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> //配置style <item name="custom_style">@style/custom_theme</item> //直接在主題中指定 <item name="custom_color1">#ff444444</item> <item name="custom_color2">#ff444444</item> <item name="custom_color3">#ff444444</item> <item name="custom_color4">#ff444444</item> <item name="custom_color5">#ff444444</item> </style> //主題中配置的style <style name="custom_theme"> <item name="custom_color1">#ff222222</item> <item name="custom_color2">#ff222222</item> <item name="custom_color3">#ff222222</item> </style> //直接在layout檔案中引用的style,最後會被放到set中 <style name="myStyle"> <item name="custom_color1">#ff111111</item> <item name="custom_color2">#ff111111</item> </style>
layout中如下定義:
<com.exmp.MyCustomView android:layout_width="wrap_content" android:layout_height="wrap_content" android:style="@style/myStyle" app:custom_color1="#ff000000" > </com.exmp.MyCustomView>
在MyCustomView的構造器中:
public MyCustomView(Context context) { this(context,null);}public MyCustomView(Context context, AttributeSet set) { this(context, set, R.attr.custom_style);}public LinearRecyclerView(Context context, AttributeSet set, int defStyle) { super(context, set, defStyle); final TypedArray a = context.obtainStyledAttributes( set, R.styleable.custom_attrs, defStyle, R.style.default_style);}
如上配置之後,TypedArray中擷取的屬性值分別是:
custom_color1=#ff000000 //布局檔案中直接指定,優先順序最高
custom_color2=#ff111111 //布局同通過style指定,也包含在set中,優先順序第二
custom_color3=#ff222222 //布局通過主題中配置風格style
custom_color4=#ff444444 //由系統Theme直接指定的
custom_color5=#ff444444
這裡看到我們的預設style沒有效果,原因是只有當defStyle(Theme中可配置style)不為0 而且在Theme中已經配置了defStyle時,預設style不起效果。
TypedArray
我們看到在擷取到屬性值之後,都會返回一個TypedArray對象,它又是什麼鬼?TypedArray主要有兩個作用,第一是內部去轉換attrid和屬性值數組的關係;第二是提供了一些類型的自動轉化,比如我們getString時,如果你是通過@string/hello這種方式設定的,TypedArray會自動去將ResId對應的string從資源檔中讀出來。說到底,都是為了方便我們擷取屬性參數。
例子-GitHub
回到開始
現在我們應該知道如何為我們的自訂View添加在主題中可配置的Style,主要是通過
obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)方法來做,需要注意的是,defStyleAttr和defStyleRes都可以設定成0表示不去搜尋可配置的風格和預設風格。
問題來了,如果來實現我的第二個需求為一個普通的類添加一個可以在Theme中可以配置的樣式(主要不就是為了業務方使用庫時配置或者傳入一些簡單的值,這裡不去討論這種方式的優劣,只討論可行性)?其實很簡單:
首先定義:
<attr name="config_style" reformat="referenc" /> public class ClassNeedConfig { public ClassNeedConfig(Context context) { //因為這個類不是通過Inflate進來的,所以肯定沒有set,直接給null就ok //attrs 你需要擷取的屬性,通常是自己定義的 //指定在Theme中搜尋的屬性 // 0表示不去搜尋預設的樣式 TypedArray a = context.obtainStyledAttributes(null,attrs,R.attr.config_style,0); }}
本文主要參考:
Android 深入理解Android中的自訂屬性
Android中自訂樣式與View的建構函式中的第三個參數defStyle的意義
2016-01-14更新
優先順序如下:set>defStyleAttr(主題可配置樣式)>defStyleRes(預設樣式)>NULL(主題中直接指定)
這個優先順序需要說明一點,defStyleRes只有在defStyleAttr為0或者主題中沒有配置時,才會生效;所以上面例子中
custom_color4=#ff444444 而不是333333,因為此時的defStyleAttr我們配置了。
文/楚雲之南(簡書作者)
原文連結:http://www.jianshu.com/p/61b79e7f88fc
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。
深入理解Android 自訂attr Style styleable以及其應用