Android View構造方法第三參數使用方法詳解

我們都知道,在Android中要使用一個View,一般會有兩種方式:

  1. 在XML文件中配置;
  2. 直接在代碼中new一個View的對象。

我們今天討論的內容就是圍繞著View的構造方法的梦谜。

1、實例

首先我們先來看一個例子。

新建一個工程唁桩,layout文件如下:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/layout"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <Button  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="(Context, AttributeSet)" />  
  
</LinearLayout>  

Activity:

protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.three_button_layout);  
  
    Button btn1 = new Button(this);  
    btn1.setText("(Context)");  
    Button btn2 = new Button(this, null, 0);  
    btn2.setText("(Context, AttributeSet, int)");  
  
    LinearLayout layout = (LinearLayout) findViewById(R.id.layout);  
    layout.addView(btn1);  
    layout.addView(btn2);  
}  

在layout文件中有一個Button闭树,然后在代碼中new了兩個Button,并且添加到layout文件中荒澡,顯示結果如下:
Demo截圖顯示

很顯然报辱,前面兩個Button樣式是一樣的,并且默認可以點擊单山,第3個Button就有點奇怪了碍现,而且還無法點擊。為什么會出現這種現象呢米奸?這就是這篇文章要說明的問題了昼接。

View的構造函數

要想理解上面的問題,我們必須先得了解View的構造函數悴晰。默認情況下慢睡,View有3個構造函數,函數原型如下:

/**
     * 在Code中實例化一個View就會調用這個構造函數
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can
     *        access the current theme, resources, etc.
     */
    public View(Context context);  

  /**
     * 在xml中定義會調用這個構造函數
     * Constructor that is called when inflating a view from XML. This is called
     * when a view is being constructed from an XML file
     */
    public View(Context context, AttributeSet attrs);  


    public View(Context context, AttributeSet attrs, int defStyle); 
  • 如果要在代碼中new一個View對象铡溪,我們一般會使用第一個構造函數漂辐。
  • 如果是在XML文件中聲明的View,系統會默認調用第二個構造函數棕硫。
  • 而對于第三個構造函數髓涯,我們在自己的代碼中一般都沒有去調用它。

在上面的例子中哈扮,btn2這個Button正是采用的第三種構造方法創(chuàng)建出來的纬纪,結果導致了很奇怪的結果。既然是用Button做的例子灶泵,我們來看下Button的源碼(Button的源碼可以說是所有Android自帶控件中最簡單的了吧):

public class Button extends TextView {  
    public Button(Context context) {  
        this(context, null);  
    }  
  
    public Button(Context context, AttributeSet attrs) {  
        this(context, attrs, com.android.internal.R.attr.buttonStyle);  
    }  
  
    public Button(Context context, AttributeSet attrs, int defStyle) {  
        super(context, attrs, defStyle);  
    }  
}  

我們可以看到育八,整個類中僅僅只有3個構造方法对途,但是它繼承自TextView赦邻,所以它的各種方法都是在TextView中實現的。然而实檀,我們平時看到的TextView和Button還是有很多地方不同的惶洲,那是什么地方導致的這些差異呢?

顯然膳犹,除了第二個構造方法中的com.android.internal.R.attr.buttonStyle恬吕,不可能有其他地方來區(qū)分TextView和Button了。而這里第二個構造方法調用了第三個構造方法须床,第三個構造比第二個構造方法多了一個int類型的參數铐料。這就是關鍵所在了。

View構造方法中的第三個參數。

我們來看一下第三個構造方法的官方文檔注釋:

Perform inflation from XML and apply a class-specific base style. This constructor of View allows subclasses to use their own base style when they are inflating. 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.

對第三個參數的解釋是:

An attribute in the current theme that contains a reference to a style resource to apply to this view. If 0, no default style will be applied.

它的大概意思就是钠惩,給View提供一個基本的style柒凉,如果我們沒有對View設置某些屬性,就使用這個style中的屬性篓跛。

繼續(xù)用Button來分析膝捞。

通過Button第3個構造方法的調用,我們來到TextView的構造方法中愧沟,當中有一句關鍵代碼:

TypedArray a =  context.obtainStyledAttributes(  
        attrs, com.android.internal.R.styleable.TextView, defStyle, 0);  

接下來蔬咬,我們分析一下obtainStyledAttributes方法。

obtainStyledAttributes

跟蹤該方法沐寺,發(fā)現最終調用的是Resources.Theme類中的obtainStyledAttributes()方法林艘,該方法里面主要是通過調用一個native方法來拿到控件的屬性,放到TypedArray中混坞。

 public TypedArray obtainStyledAttributes(AttributeSet set,
                @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
            return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
        }

我們來仔細閱讀一下obtainStyledAttributes()方法的官方文檔

obtainStyledAttributes()方法的官方文檔

  • set:在XML中明確寫出了的屬性集合北启。(比如android:layout_width、android:text="@string/hello_world"這些)
  • attrs:需要在上面的set集合中查詢哪些內容拔第。如果是自定義View咕村,一般會把自定義的屬性寫在declare-styleable中,代表我們想查詢這些自定義的屬性值蚊俺。
  • defStyleAttr:這是一個定義在attrs.xml文件中的attribute懈涛。這個值起作用需要兩個條件:1. 值不為0;2. 在Theme中使用了(出現即可)泳猬。
  • defStyleRes:這是在styles.xml文件中定義的一個style批钠。只有當defStyleAttr沒有起作用,才會使用到這個值得封。
    這還是一個比較模糊的概念埋心,我們來看看系統里面是怎么使用這些值的。

首先找到frameworks\base\core\res\res\values目錄下的attrs.xml忙上、styles.xml拷呆、themes.xml三個文件,打開疫粥。

既然Button的構造方法中使用到了com.android.internal.R.attr.buttonStyle茬斧,我們就來看看這個attr。該attr位于attrs.xml中:

<attr name="buttonStyle" format="reference" />  

只是簡單的定義了一個attr梗逮。

然后在哪里用到了它呢项秉?看到themes.xml文件下,有這樣一個style:

<style name="Theme">  
...  
   <item name="buttonStyle">@android:style/Widget.Button</item>  
...  
</style> 

在這里用到了buttonStyle屬性慷彤,它指向另外一個style娄蔼,這個style在styles.xml文件下:

<style name="Widget.Button">  
    <item name="android:background">@android:drawable/btn_default</item>  
    <item name="android:focusable">true</item>  
    <item name="android:clickable">true</item>  
    <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>  
    <item name="android:textColor">@android:color/primary_text_light</item>  
    <item name="android:gravity">center_vertical|center_horizontal</item>  
</style>  

我們可以看到怖喻,這里面的屬性都是用來配置Button的。如果在XML文件中沒有給Button配置背景岁诉、內容的位置等屬性罢防,就會默認使用這里的屬性。當然這是在使用了defStyleAttr的情況才會出現的唉侄,這也解釋了文章開頭的例子中的奇怪現象了咒吐。

千萬不要以為這樣就萬事大吉了,現在我們只是定義好了這些屬性属划,并沒有使用到它恬叹。那在哪里使用到的呢?注意上面的themes.xml中的那個style的名稱為Theme同眯,而在我們自己的工程中绽昼,在配置menifest文件的時候,給application或者activity設置的主題android:theme一般都是這個style的子類须蜗,所以也就這樣使用到了defStyleAttr定義的屬性了硅确。至于是如何拿到這些屬性的,我想是在obtainStyledAttributes()方法中處理的明肮,這里不需要過多追究菱农。

還有一個defStyleRes參數,我們可以發(fā)現在TextView柿估、ImageView等控件中循未,這個值傳的都是0,也就是不使用它秫舌。它的作用就像是一個替補的妖,當defStyleAttr不起作用的時候它就上場,因為它也是一個style足陨,這個參數是怎么起作用的在下面的實例中有提到嫂粟。

實例

上面的都是理論,我們接下來用一個例子來實踐一下墨缘。

首先創(chuàng)建一個attrs.xml文件:(如果還不會自定義View屬性的星虹,請參考

Android 自定義View 之 自定義View屬性

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
  
    <declare-styleable name="CustomView">  
        <attr name="attr1" format="string" />  
        <attr name="attr2" format="string" />  
        <attr name="attr3" format="string" />  
        <attr name="attr4" format="string" />  
        <attr name="attr5" format="string" />  
        <attr name="attr6" format="string" />  
    </declare-styleable>  
  
    <attr name="customViewStyle" format="reference" />  
  
</resources>  

注意,這里即使將customViewStyle屬性寫在declare-styleable里飒房,最終效果也一樣搁凸。

定義style媚值。

首先定義我們的defStyleAttr屬性(在本項目中是customViewStyle屬性)需要用到的style(位于styles.xml文件中):

<style name="custom_view_style">  
    <item name="attr3">attr3 from custom_view_style</item>  
    <item name="attr4">attr4 from custom_view_style</item>  
</style>  

然后定義一個在xml布局文件中需要用到的style(位于styles.xml文件中):

<style name="xml_style">  
    <item name="attr2">attr2 from xml_style</item>  
    <item name="attr3">attr3 from xml_style</item>  
</style>  

自定義一個簡單的View:

public class CustomView extends View {  
      
    static final String LOG_TAG = "CustomView";  
      
    public CustomView(Context context) {  
        this(context, null);  
    }  
  
    public CustomView(Context context, AttributeSet attrs) {  
        this(context, attrs, R.attr.customViewStyle);  
    }  
  
    public CustomView(Context context, AttributeSet attrs, int defStyle) {  
        super(context, attrs, defStyle);  
          
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, 0);  
          
        Log.d(LOG_TAG, "attr1 => " + array.getString(R.styleable.CustomView_attr1));  
        Log.d(LOG_TAG, "attr2 => " + array.getString(R.styleable.CustomView_attr2));  
        Log.d(LOG_TAG, "attr3 => " + array.getString(R.styleable.CustomView_attr3));  
        Log.d(LOG_TAG, "attr4 => " + array.getString(R.styleable.CustomView_attr4));  
        Log.d(LOG_TAG, "attr5 => " + array.getString(R.styleable.CustomView_attr5));  
        Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.CustomView_attr6));  
    }  
      
}  

注意這里用到了R.attr.customViewStyle狠毯。為了使它生效,需要在當初工程的theme中設置它的值(位于styles.xml文件中):

<!-- Application theme. -->  
<style name="AppTheme" parent="AppBaseTheme">  
    <!-- All customizations that are NOT specific to a particular API-level can go here. -->  
    <item name="customViewStyle">@style/custom_view_style</item>  
</style>  

這里就用到了我們上面定義的custom_view_style這個style褥芒。

運行結果:
運行結果

分析:
  • attr1只在xml布局文件中設置嚼松,所以值為attr1 from xml嫡良。
  • attr2在xml布局文件和xml style中都設置了,取值為布局文件中設置的值献酗,所以為attr2 from xml寝受。
  • attr3沒有在xml布局文件中設置,但是在xml style和defStyleAttr定義的style中設置了罕偎,取xml style中的值很澄,所以值為attr3 from xml_style。
  • attr4只在defStyleAttr定義的style中設置了颜及,所以值為attr4 from custom_view_style甩苛。
  • attr5和attr6沒有在任何地方設置值,所以為null俏站。

這也證實了前面所得出的順序是正確的讯蒲。

我們再來測試一下defStyleRes這個參數,它是一個style肄扎,所以添加一個style(位于styles.xml文件中):

<style name="default_view_style">  
    <item name="attr4">attr4 from default_view_style</item>  
    <item name="attr5">attr5 from default_view_style</item>  
</style>  

然后還需要修改CustomView中的第16行墨林,為下面一行:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, R.style.default_view_style);  

運行結果:
運行結果

咦,為什么結果和上面一樣呢犯祠?

我們看到官方文檔中對obtainStyledAttributes()方法的defStyleRes參數解釋是這樣的:

A resource identifier of a style resource that supplies default values for the TypedArray, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.

也就是說旭等,當defStyleAttr這個參數定義為0(即不使用這個參數),或者是在theme中找不到defStyleAttr這個屬性時(即使在theme中的配置是這樣的:<item name="defStyleAttr">@null</item>衡载,也代表找到了defStyleAttr屬性辆雾,defStyleRes參數也不會生效),defStyleRes參數才會生效月劈。
所以我們修改CustomView為下面內容(或者是去掉theme中對customViewStyle的使用):

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView, 0, R.style.default_view_style);  

運行結果:
運行結果

由于defStyleAttr已經失效度迂,所以attr4和attr5都是從default_view_style中獲取到的值。

我們知道猜揪,在theme所在的style中也可以設置屬性惭墓,如下:

<!-- Application theme. -->  
<style name="AppTheme" parent="AppBaseTheme">  
    <!-- All customizations that are NOT specific to a particular API-level can go here. -->  
    <item name="customViewStyle">@style/custom_view_style</item>  
    <item name="attr5">attr5 from AppTheme</item>  
    <item name="attr6">attr6 from AppTheme</item>  
</style>  

運行結果:
運行結果

attr1~attr4不用說了。
attr5在default style和theme下都定義了而姐,取default style下的值腊凶,所以為attr5 from default_view-style。
attr6只在theme下定義了拴念,所以取值為attr6 from AppTheme钧萍。

注意,如果將CustomView中重新改成下面的內容(即使customViewStyle生效):

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, 0); 

這時政鼠,default style是失效了的风瘦,那么在theme中設置的值會不會生效呢?

看運行結果:
運行結果

attr5在default style和theme下都定義了公般,但default style失效了万搔,這里并沒有因為customViewStyle是有效的而忽略theme中設置的值胡桨,所以為attr5 from AppTheme。
attr6只在theme下定義了瞬雹,同樣沒有因為customViewStyle是有效的而忽略theme中設置的值昧谊,所以取值為attr6 from AppTheme。

這里和default style的取值形式有一點點不同酗捌。

總結

View中的屬性有多處地方可以設置值呢诬,這個優(yōu)先級是:

  • 1、直接在XML布局文件中設置的值優(yōu)先級最高胖缤,如果這里設置了值馅巷,就不會去取其他地方的值了。
  • 2草姻、XML布局文件中有一個叫“style”的屬性钓猬,它指向一個style,在這個style中設置的屬性值優(yōu)先級次之撩独。
  • 3敞曹、如果上面兩個地方都沒有設置值,那么就會根據View帶三個參數的構造方法中的第三個參數attribute指向的style設置值综膀,前提是這個attribute的值不為0澳迫。
  • 4、如果上面的attribute設置為0了剧劝,我們就根據obtainStyledAttributes()方法中的最后一個參數指向的style來設置值橄登。
  • 5、如果仍然沒有設置到值讥此,就會用theme中直接設置的屬性值拢锹,而不會去管第3步和第4步中是否設置了值。

必須要注意:要想讓View構造方法的第三個參數生效萄喳,必須讓它出現在我們自己的Application或者Activity的android:theme所指向的style中卒稳。設置Activity的theme一樣可以。

參考文章:

Android中style和theme的區(qū)別

View構造方法第三參數使用方法詳解

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末他巨,一起剝皮案震驚了整個濱河市充坑,隨后出現的幾起案子,更是在濱河造成了極大的恐慌染突,老刑警劉巖捻爷,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異份企,居然都是意外死亡也榄,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門薪棒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來手蝎,“玉大人榕莺,你說我怎么就攤上這事俐芯】媒椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵吧史,是天一觀的道長邮辽。 經常有香客問我,道長贸营,這世上最難降的妖魔是什么吨述? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮钞脂,結果婚禮上揣云,老公的妹妹穿的比我還像新娘。我一直安慰自己冰啃,他們只是感情好邓夕,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阎毅,像睡著了一般焚刚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扇调,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天矿咕,我揣著相機與錄音,去河邊找鬼狼钮。 笑死碳柱,一個胖子當著我的面吹牛,可吹牛的內容都是我干的熬芜。 我是一名探鬼主播士聪,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼猛蔽!你這毒婦竟也來了剥悟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤曼库,失蹤者是張志新(化名)和其女友劉穎区岗,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體毁枯,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡慈缔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了种玛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藐鹤。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓤檐,死狀恐怖,靈堂內的尸體忽然破棺而出娱节,到底是詐尸還是另有隱情挠蛉,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布肄满,位于F島的核電站谴古,受9級特大地震影響,放射性物質發(fā)生泄漏稠歉。R本人自食惡果不足惜掰担,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怒炸。 院中可真熱鬧带饱,春花似錦、人聲如沸阅羹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灯蝴。三九已至恢口,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間穷躁,已是汗流浹背耕肩。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留问潭,地道東北人猿诸。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像狡忙,于是被迫代替她去往敵國和親梳虽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容