深入理解Android 自訂attr Style styleable以及其應用

來源:互聯網
上載者:User

標籤:

相信每一位從事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)方法來做,需要注意的是,defStyleAttrdefStyleRes都可以設定成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以及其應用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.