view的構造函數中的參數

SurfaceView是View的子類,與View一樣有三個constructor:

1 public void CustomView(Context context{}
2 public void CustomView(Context context, AttributeSet attrs{}
3 public void CustomView(Context context, AttributeSet attrs, int defStyle{}

  爲了方便,我們分別命名爲C1,C2,C3。
  C1是最簡單的一個,如果你只打算用code動態創建一個view而不使用佈局文件xml inflate,那麼實現C1就可以了。
  C2多了一個AttributeSet類型的參數,在通過佈局文件xml創建一個view時,這個參數會將xml裏設定的屬性傳遞給構造函數。如果你採用xml inflate的方法卻沒有在code裏實現C2,那麼運行時就會報錯。但是由於編譯能順利通過,對於我這樣的菜鳥,這個錯誤有時不太容易被發現。
  關於C1和C2,google和度娘上都有很多文章介紹,我就不做贅述。

  扯淡的是C3。
  C3多了一個defStyle的int參數,關於這個參數doc裏是這樣描述的:

  The default style to apply to this view. If 0, no style will be applied (beyond what is included in the theme). This may either be an attribute resource, whose value will be retrieved from the current theme, or an explicit style resource.

  從字面上翻譯,這個參數似乎是用來指定view的默認style的,如果是0,那麼將不會應用任何默認(或者叫缺省)的style。另外這個參數可以是一個屬性指定的style引用,也可以直接是一個顯式的style資源。

  這僅僅是字面上翻譯的結果,就已經不太好理解了。我琢磨了一下,大概有這麼兩個問題:

1. 這個C3什麼時候會被調用?
  C1是代碼創建view時,C2是xml創建view時,那麼C3呢?既然defStyle是一個與指定style有關的參數,那麼一個比較自然的猜想是當在代碼比如xml裏通過某種方式指定了view的style時,C3在該view被inflate時調用,並將style傳入給defStyle。
  那麼在xml裏指定style有幾種方式呢?大概有兩種,一種是在直接在佈局文件該view標籤裏使用
1 style="@style/customstyle"
來指定,另一種是採用指定theme的方式,在AndroidManifest.xml的application標籤裏使用
1 android:theme="@style/customstyle"
這兩種方式都需要在res/values/styles.xml裏定義customstyle:

1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3     <style name="customstyle">
4         <item name="android:background">@drawable/bg</item>
5         [... or other style code...]
6     </style>
7 </resources>

注:使用theme時標準的theme定義方式是把style放在themes.xml裏而不是styles.xml,但實際上R.java在生成時無論是themes.xml和styles.xml裏的style都是同質的,都存在於R.style下。

回到C3的問題上來,那麼這兩種指定style的方式會不會觸發C3呢?很遺憾,經測試,不會。並且至今我沒發現任何一種情況會自動地(隱式地)調用構造函數C3……不知道究竟有沒有這種情況存在呢?

那麼C3到底什麼時候被調用呢?答案是當你顯式調用它的時候。通常是在C1或者C2裏,用

1 public void CustomView(Context context, AttributeSet attrs{
2     this(context, attrs, resid);
3 }

的方式將真正構造函數的實現轉移到C3裏,並由resid指定defStyle,作爲默認style。比如android源碼中button的實現:

  For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle for defStyle; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.

(摘自C3的doc)這裏的R.attr.buttonStyle就是一個resid。於是引出了第二個問題。

2. defStyle接受什麼樣的值?
  你可能會說,doc上不是寫着呢麼?這個參數可以是一個屬性指定的style引用,也可以直接是一個顯式的style資源。
  那我們就試驗一下看看。
  首先在res/styles.xml裏定義一個style:

1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3     <style name="purple">
4         <item name="android:background">#FFFF00FF</item>
5     </style>
6 </resources>

  然後自定義一個View(或者SurfaceView也是可以的): CustomView.java

01 package com.your.test;
02
03 public class CustomView extends View {
04
05     //C1
06     public CustomView(Context context{
07         super(context);
08     }
09
10     //C2
11     public CustomView(Context context, AttributeSet attrs{
12         this(context, attrs, 0);
13     }
14
15     //C3
16     public CustomView(Context context, AttributeSet attrs, int defStyle{
17         super(context, attrs, defStyle);
18     }
19 }

  之後是佈局文件layout/main.xml:

1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3               xmlns:myxmlns="http://schemas.android.com/apk/res/com.your.test"
4               android:orientation="vertical"
5               android:layout_width="fill_parent"
6               android:layout_height="fill_parent" >
7      <com.your.test.CustomView android:layout_width="100px"
8                                android:layout_height="100px" />
9 </LinearLayout>

  最後是main activity文件Test1.java:

1 package com.your.test;
2
3 public class Test extends Activity {
4     @Override
5     public void onCreate(Bundle savedInstanceState{
6         super.onCreate(savedInstanceState);
7         setContentView(R.layout.main);
8     }
9 }

把該import的import了,運行應該能看到一個正常的黑色背景的view。
下面應用我們定義的style試試看:

1 <com.your.test.CustomView android:layout_width="100px"
2                           android:layout_height="100px"
3                           style="@style/purple"
4 />

view的背景變成了紫色,但如果你log一下就會發現,調用的還是C2。
在AndroidManifest.xml裏用theme指定,結果也差不多(細節差別可自己體會,不贅述)。

  下面我們就來研究defStyle到底接受什麼樣的參數。
  首先把style和theme的引用都去掉,還原到黑色背景的view。這樣在程序裏R.style.purple就是這個style的顯式引用(其實到現在我也不知道doc裏說的explicit style resource是不是就是這個意思……)那麼,理論上我們把R.style.purple當作defStyle傳給C3,是不是就能做到設定view的默認背景爲紫色呢?

1 public CustomView(Context context, AttributeSet attrs{
2     this(context, attrs, R.style.purple);
3 }

如果你log一下,就會發現,C2確實執行了,甚至R.style.purple也成功傳給C3裏的defStyle了,但是,view的背景還是黑色。
  這是爲什麼呢?是doc不對還是我不對?
  這個先暫且放下不談,我們先試試那另外一種方式,傳入一個引用style資源的屬性(類似R.attr.buttonStyle)。這先要創建一個res/values/attrs.xml的文件,這個文件用來定義某個view裏可以出現的屬性:

1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3     <declare-styleable name="CustomView">
4         <attr name="ourstyle" format="reference" />
5         <attr name="atext" format="string" />
6     </declare-styleable>
7 </resources>

現在我們爲CustomView增加了兩個可以出現的自定義屬性,ourstyle和atext,前者就是我們打算用來引用一個style資源的屬性,後者是一個沒什麼用的字符串屬性,放在這裏只是爲了後面做測試。
現在我們就可以在程序裏引用這個屬性並把這個參數傳給defStyle。

  當然,在這之前我們先要把purple這個style賦值給ourstyle。給一個view的屬性賦值,就和給android:layout_width賦值一樣,除了命名空間不同(layout/main.xml的LinearLayout標籤裏有命名空間的聲明):

1 <com.your.test.CustomView android:layout_width="100px"
2                           android:layout_height="100px"
3                           myxmlns:ourstyle="@style/purple"
4                           myxmlns:atext="test string"
5 />

也可以用指定theme的方法,在theme裏給所有的CustomView都賦予一個相同的默認的ourstyle值,然後應用這個theme:

在styles.xml裏另外定義一個style作爲theme:

1 <style name="purpletheme">
2     <item name="ourstyle">@style/purple</item>
3 </style>

在AndroidManifest.xml的Application標籤中應用theme:
1 android:theme="style/purpletheme"
這兩種指定屬性的方法不同,在程序裏引用這個屬性的方法也不同。theme指定的屬性,可以直接用R.attr.ourstyle來引用,也可以用R.styleable.CustomView[R.styleable.CustomView_ourstyle]來引用,於是:

1 //C2
2 public CustomView(Context context, AttributeSet attrs{
3     this(context, attrs, R.attr.ourstyle );
4 }

這樣就成功地讓defStyle生效了。

那麼直接在標籤裏賦值的屬性怎麼引用呢?
直接在標籤裏賦值的屬性,都會在xml inflate時通過AttributeSet這個參數傳給C2,所以我們可以通過AttributeSet類提供的getAttributeResourceValue方法來獲取屬性的值。但是很可惜的是,我們只能獲取到屬性的值,而無法獲取包含這個值的屬性的引用(getAttributeNameResource方法返回的是和R.attr.ourstyle一樣的值,但這時R.attr.ourstyle並未指向@style/purple),這些亂七八糟的方法的各種值之間具體差別可以參考以下代碼的log結果,相信仔細揣摩不難明白其中奧妙:

01 //C2
02 public CustomView(Context context, AttributeSet attrs{
03     this(context, attrs, attrs.getAttributeNameResource(2));
04     String a1 = ((Integer)R.attr.ourstyle).toString();
05     String a2 = ((Integer)R.styleable.CustomView_ourstyle).toString();
06     String a3 = ((Integer)R.styleable.CustomView[R.styleable.CustomView_ourstyle]).toString();
07     String a4 = ((Integer)R.style.purple).toString();
08     String a5 = ((Integer)attrs.getAttributeNameResource(2)).toString();
09     String a6 = ((Integer)attrs.getAttributeResourceValue(2,0)).toString();
10     String a7 = ((Integer)R.attr.atext).toString();
11     String a8 = ((Integer)R.styleable.CustomView[R.styleable.CustomView_atext]).toString();
12     String a9 = attrs.getAttributeValue(2);
13     String a10 = attrs.getAttributeValue(3);
14 }

log一下a1-10,示例結果如下:

01 a1 = 2130771968
02 a2 = 0
03 a3 = 2130771968
04 a4 = 2131034112
05 a5 = 2130771968
06 a6 = 2131034112
07 a7 = 2130771969
08 a8 = 2130771969
09 a9 = @2131034112
10 a10 = test string

凡是值相同的其實是一種意思,a1, a3, a5都指的是attrs.xml裏屬性的引用,這個引用id只有在theme裏賦值纔有效,直接在標籤裏賦值是無效的。而傳這個id給defStyle就正符合doc裏寫的第一種情況。而a4, a6則直接代表了@style/purple的id,即doc裏寫的第二種情況
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章