天天看點

深入了解Android 自定義attr Style styleable以及其應用

相信每一位從事<code>android</code>開發的猿都遇到過需要自己去自定義<code>view</code>的需求,如果想通過<code>xml</code>指定一些我們自己需要的參數,就需要自己聲明一個<code>styleable</code>,并在裡面自己定義一些<code>attr</code>屬性,這個過程相信大家都比較了解。當然,屬性其實也不一定需要和<code>view</code>配合使用,比如我想通過一個<code>theme</code>中的<code>style</code>對一個庫進行一些簡單參數的配置,這應該怎麼做呢?我今天在封裝一個庫時在這個地方浪費了較多時間,最後沒辦法,到處搜搜資料,記錄在這裡吧,相信對大家都有幫助。

首先要明确一點,<code>attr</code>不依賴于<code>styleable</code>,<code>styleable</code>隻是為了友善<code>attr</code>的使用。

我們自己定義屬性完全可以不放到<code>styleable</code>裡面,比如直接在resources檔案中定義一些屬性:

定義一個<code>attr</code>就會在r檔案裡面生成一個id,那麼我們去擷取這個屬性時,必須調用如下代碼:

而通過定義一個<code>styleable</code>,我們可以在r檔案裡自動生成一個int[],數組裡面的int就是定義在<code>styleable</code>裡面的<code>attr</code>的id。是以我們在擷取屬性的時候就可以直接使用<code>styleable</code>數組來擷取一系列的屬性。

擷取:

由上面的例子可以知道,定義一個<code>declare-styleable</code>,在擷取屬性的時候為我們自動提供了一個屬性數組。此外,我覺得使用<code>declare-styleable</code>的方式有利于我們我們把相關的屬性組織起來,有一個分組的概念,屬性的使用範圍更加明确。

其實我們在前面已經使用了<code>obtainstyledattributes</code>來擷取屬性了,現在來看看這個函數的聲明吧:

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)

//這是最複雜的一種情況,後面細說。

這麼多重載的方法是不是已經看懵了?其實你隻需要了解其中的參數就能掌握各個方法的使用方法。所謂擷取屬性,無非就是需要兩個參數:第一,我需要擷取那些屬性;第二:我從哪裡去擷取這些屬性(資料源)。

<code>attrs</code>:int[],每個方法中都有的參數,就是告訴系統需要擷取那些屬性的值。

<code>set</code>:表示從<code>layout</code>檔案中直接為這個<code>view</code>添加的屬性的集合,如:<code>android:layout_width="match_parent"。注意,這裡面的屬性必然是通過</code>xml<code>配置添加的,也就是由</code>layoutinflater<code>加載進來的布局或者</code>view`才有這個屬性集。

現在你知道為啥我們在自己定義view的時候至少要重寫(context context, attributeset set)構造器了吧?因為不重寫時,我們将無法擷取到layout中配置的屬性!!當然,也因為這樣,layoutinflater在inflater布局時會通過反射去調用view的(context context, attributeset attrs)構造器。 set 中實際上又有兩種資料來源,當然最後都會包含在set中。一種是直接使用<code>android:layout_width="wrap_content"</code>這種直接指定的,還有一種是通過<code>style="@style/somestyle"</code>這樣指定的。

這個參數是本文的關鍵所在,也是自定義一個可以在<code>theme</code>中配置的樣式的關鍵,先看個栗子吧:

如果我想通過在系統主題裡面設定一個樣式,修改所有<code>textview</code>的樣式,你一般會這麼做:

首先<code>android:textviewstyle</code>其實就是一個普通的在資源檔案中定義的屬性<code>attr</code>,它的<code>format="reference"</code>。那問題來了,<code>textview</code>是怎麼得知我們自己定義的<code>textviewstyle</code>的呢?這其實就是<code>defstyleattr</code>的應用場景:定義theme可配置樣式。

<code>resid or defstyleres</code>:直接從資源檔案中定義的某個樣式中讀取。

看看第二個方法吧,裡面除了指定了<code>attrs</code>屬性集之外沒有任何屬性值來源,資料從哪兒來呢?原來我們可以直接在theme中指定屬性的值,那麼<code>null</code>表示直接從<code>theme</code>中讀取屬性。

是不是看到這裡你已經有點迷糊了?不要緊,耐心看下去,後面有一個例子,看完例子你再回頭看看這裡的說明就ok了。

看看這個方法,傳回的結果還是我們所關心的attrs(int[])中包含的屬性集。那麼資料來源呢?一共有4個,<code>set</code>,<code>defstyleattr</code>,<code>null</code>,<code>defstyleres</code>,如果一個屬性在多個地方都被定義了,那麼以哪個為準?

優先級如下:

<code>set</code>&gt;<code>defstyleattr</code>(主題可配置樣式)&gt;<code>defstyleres</code>(預設樣式)&gt;<code>null</code>(主題中直接指定)

<code>attr</code>資源檔案中如下定義:

<code>styles</code>資源檔案中如下定義:

layout中如下定義:

在mycustomview的構造器中:

如上配置之後,<code>typedarray</code>中擷取的屬性值分别是:

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主要有兩個作用,第一是内部去轉換<code>attr</code>id和屬性值數組的關系;第二是提供了一些類型的自動轉化,比如我們<code>getstring</code>時,如果你是通過<code>@string/hello</code>這種方式設定的,<code>typedarray</code>會自動去将<code>resid</code>對應的<code>string</code>從資源檔案中讀出來。說到底,都是為了友善我們擷取屬性參數。

<a target="_blank" href="https://github.com/chuyun923/customattrexmp">例子下載下傳位址-github</a>

現在我們應該知道如何為我們的自定義<code>view</code>添加在主題中可配置的<code>style</code>,主要是通過

<code>obtainstyledattributes (attributeset set, int[] attrs, int defstyleattr, int defstyleres)</code>方法來做,需要注意的是,<code>defstyleattr</code>和<code>defstyleres</code>都可以設定成0表示不去搜尋可配置的風格和預設風格。

問題來了,如果來實作我的第二個需求為一個普通的類添加一個可以在theme中可以配置的樣式(主要不就是為了業務方使用庫時配置或者傳入一些簡單的值,這裡不去讨論這種方式的優劣,隻讨論可行性)?其實很簡單:

首先定義:

繼續閱讀