徹底搞懂自定義控制元件中的四個構造方法
在上一篇部落格動手實現餅圖控制元件寫完以後,有些小夥伴說講得不夠細,建議從最基本開始講起,比如建構函式都是什麼?我覺得說得很有道理,正好自己也不夠了解自定義控制元件中的4個構造方法的具體呼叫時機和它們各自的引數作用,今天終於有時間把這部分內容進行學習整理,順便分享給那些和我一樣不太瞭解它們的同學們!
首先我們先將構造方法中的引數的作用做個說明,來看一下引數最多的構造方法:
public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); }
一共4個引數:
(1)context:這個不用多說了,大家都知道,上下文物件.
(2)attrs:屬性集合,但是它指的是什麼呢到底,抱著能動手絕不嗶嗶的態度,我直接將它打印出來:
實驗:
先看一下佈局檔案的情況:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="match_parent"> <huanju.wanka.com.MyCustomView xmlns:myapp="http://schemas.android.com/apk/res-auto" android:layout_width="300dp" android:layout_height="300dp" myapp:attr1="我是attr1,我是在xml中定義的" style="@style/theme_style" /> </RelativeLayout>
接著,我們列印一下attrs:
第3個是一個自定義屬性,暫時先不用管它,主要想說明的是,這裡打印出了我們在layout中對這個view設定的所有屬性.這也就和它的名字對上了,"AttributeSet"——屬性集合.
(3)defStyleAttr:這個引數是指,在當前view所屬Activity的Theme(或如果這個Activity沒有設定Theme,那麼就是指Application的Theme)對控制元件設定的屬性
實驗:
先自定義一個theme,同時給自定義屬性加上一個"attr2",
<style name="theme_style" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="attr1">我是attr1,我是在theme中定義的</item> <item name="attr2">我是attr2,我是在theme中定義的</item> <item name="android:background">#00ff00</item> </style>
然後將這個theme設定給當前的MainActivity:
<activity android:name="huanju.wanka.com.MainActivity" android:theme="@style/theme_style"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
之前列印的方式是列印attrs這個引數,但是它是代表我們在佈局中對view設定的屬性,而這一步我們是通過theme設定的屬性,所以只能通過程式碼中獲取屬性再逐個列印,列印結果:
看結果~發現attr1的結果好像不太對勁,這裡就涉及到設定屬性的優先順序了.由於我們在xml中對attr1屬性進行了設定,那麼它優先取了xml中定義的結果,而attr2在xml佈局中並沒有被定義,所以它取了theme中的結果.
其實講到這裡有些細心的同學會有疑問了,你這是通過theme直接將一個style直接設定給了Activity,又不是通過構造方法設定給它的.確實是這樣,前面為了方便觀察屬性,我是方便了一下,程式碼中defStyleAttr接收的也是一個int型別,我們可以通過R.style.xxx設定給它.
(4)defStyleRes:這個引數在構造方法中接收的一樣是一個int型別的style,我們依然可以按照R.style.xxx的方式設定給它,那它豈不是和上面的第3個引數衝突了嗎?我們還是通過動手實驗來看看結果吧.
public MyCustomView(Context context, @Nullable AttributeSet attrs) { this (context, attrs, R.style.theme_style); } public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, R.style.my_view_default_resource); }
我先在第2個構造方法中手動呼叫了第3個構造,並對第3個引數設定了一個style.
<style name="theme_style" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="attr1">我是theme_style的attr1</item> <item name="attr2">我是theme_style的attr2</item> </style>
接下來又在第3個構造中呼叫了第4個構造,並對第4個引數也設定了一個style.那麼此時後面2個引數都是有設定值的.
<style name="my_view_default_resource" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="attr1">我是my_view_default_resource的attr1</item> <item name="attr2">我是my_view_default_resource的attr2</item> </style>
執行列印看結果:
第一個引數依然是在佈局xml中定義的,之前已經說過了.可以看到attr2和attr3都是theme_style的值.根據官方對defStyleRes的說明,只有在defStyleAttr沒有匹配到或置為0時,defStyleRes才會起作用.我們來試一把,將defStyleAttr設定為0跑一下.由於我上面的呼叫關係,直接將2個引數的構造方法中呼叫第3個構造方法的第3個引數改為0.
public MyCustomView(Context context, @Nullable AttributeSet attrs) { this (context, attrs, 0); }
走你~執行,看結果:
列印的後面2個結果,很明顯了.
最後,總結一下,對view設定屬性有4個途徑,分別是:
1.直接在xml對其定義.
2.在xml中用style對view的屬性進行定義.
3.通過theme對其定義.
4.如果第3條沒滿足或被置為0,可定義一個style設定給它.
至此,我的理解就是一層層的對控制元件做些預設的屬性處理,像系統的Button那樣,我們平時用到Button控制元件時,並不需要對它設定背景,而它被展示出來總是帶著一層灰色的背景,使它看上去更像一個按鈕,這其實也是android系統為它做的預設屬性.我們在自定義控制元件時,也可以通過上面那些途徑對我們的自定義屬性做一些預設的設定.所以,既然是預設的設定,肯定是在展示的時候預設就有的,這樣我們就可以在構造方法中,通過1呼叫2,2呼叫3,3再呼叫4這樣將我們的屬性在控制元件的內部就設定好,而外界並不知道,也不需要知道.其實android系統的控制元件也是這樣去做一個個通過this呼叫建構函式的.
好了,本篇內容就講到這裡,內容可能有些繞,大家可以反覆多看兩次,再加上自己動手去試,我想大家很快就會明白這些內容.如果哪裡理解得不對也希望大家指正.我們下次見.