1. 程式人生 > >徹底搞懂自定義控制元件中的四個構造方法

徹底搞懂自定義控制元件中的四個構造方法

    在上一篇部落格動手實現餅圖控制元件寫完以後,有些小夥伴說講得不夠細,建議從最基本開始講起,比如建構函式都是什麼?我覺得說得很有道理,正好自己也不夠了解自定義控制元件中的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呼叫建構函式的.

    好了,本篇內容就講到這裡,內容可能有些繞,大家可以反覆多看兩次,再加上自己動手去試,我想大家很快就會明白這些內容.如果哪裡理解得不對也希望大家指正.我們下次見.