1. 程式人生 > >Android中自定義樣式與View的建構函式中的第三個引數defStyle的意義

Android中自定義樣式與View的建構函式中的第三個引數defStyle的意義

零、序

零、序

  系統自帶的View可以在xml中配置屬性,對於寫的好的Custom View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置,需要以下4個步驟:

    1. 通過<declare-styleable>為自定義View新增屬性

    2. 在xml中為相應的屬性宣告屬性值

    3. 在執行時(一般為建構函式)獲取屬性值

    4. 將獲取到的屬性值應用到View

  怎麼將獲取到的屬性值應用到View就不用說了,自己定義的屬性什麼用處自己肯定是清楚的,所以接下來看一下前三點。

一、自定義Style

  通過<declare-styleable>元素宣告Custom View需要的屬性即可,下面是一個例子,檔案是res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Customize">
        <attr name="attr_one" format="string" />
        <attr name="attr_two" format="string"
/> <attr name="attr_three" format="string" /> <attr name="attr_four" format="string" /> </declare-styleable> <attr name="CustomizeStyle" format="reference" /> </resources>

  在上述xml中,我們聲明瞭Customize與CustomizeSyle,Customize包含了attr_one、attr_two、attr_three與attr_four四個attribute,CustomizeStyle也是一個attribute,但是卻沒有宣告在declare-styleable中。

  定義在declare-styleable中與直接用attr定義沒有實質的不同,上述xml中,無論attr_one - attr_four是否宣告在declare-styleable中,系統都會為我們在R.attr中生成5個attribute

public static final class attr {
    public static final int CustomizeStyle=0x7f010004;
    public static final int attr_one=0x7f010000;
    public static final int attr_two=0x7f010001;
    public static final int attr_three=0x7f010002;
    public static final int attr_four=0x7f010003;
}

  不同的是,如果宣告在declare-styleable中,系統還會為我們在R.styleable中生成相關的屬性。

public static final class styleable {
    public static final int[] Customize = {
        0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003
    };
    public static final int Customize_attr_one = 0;
    public static final int Customize_attr_two = 1;    
    public static final int Customize_attr_three = 2;
    public static final int Customize_attr_four = 3;
}

   如上所示,R.styleable.Customize是一個int[],而裡面的元素的值正好和R.attr.attr_one - R.attr.attr_four一一對應,而R.styleable.Customize_attr_one等4個值就是R.attr.attr_one-R.attr.attr_four在R.styleable.Customize陣列中的索引。這個陣列和索引在第三步執行時獲得屬性值時會用到,將attr分組宣告在declare-styleabe中的作用就是系統會自動為我們生成這些東西,如果不宣告在declare-styleable中,我們完全可以在需要的時候自己構建這個陣列,由於陣列是自己構建的,每個屬性的下標索引也就很清楚了,只是比較麻煩。以上一家之言,不過從使用及實驗難上看確實是這樣的。

二、在xml中為相應的屬性宣告屬性值

        我們知道,在xml中為屬性賦值有幾種不同的方式

        下面就分別看一下這三種方式

1. 直接在layout中使用屬性

        在xml layout中使用自定義屬性和使用系統屬性差不多,不過屬性所屬的namespace不同,比如像下面這樣。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ad="http://schemas.android.com/apk/res/com.angeldevil.customstyle"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.angeldevil.customstyle.CustomTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ad:attr_one="attr one in xml"
        style="@style/ThroughStyle"
        android:text="@string/hello_world" />

</RelativeLayout>

  像layout_width等屬性屬於android的namespace,自定義的屬性屬於當前程式的namespace,只需像宣告android的namespace一樣聲明當前程式的namespace就好,只需要把上面紅色部分的android換成當前程式的包名。應用屬性的時候也需要注意屬性的namespace。

2. 設定style並在style中設定屬性

  看上面xml中綠色的那一行,我們為CustomTextView聲明瞭一個style:ThroughStyle,這個Style很簡單,只是指定了兩個屬性的值

<style name="ThroughStyle">
    <item name="attr_one">attr one from style</item>
    <item name="attr_two">attr two from style</item>
</style>

  注意,在style中我們聲明瞭attr_one的值,同時在xml中也直接向attr_one賦了值,最終用哪一個有個優先順序的問題,後面在第三節:在執行時獲取屬性值中再說,接下來看下第三種方式。

3. theme中指定在當前Application或Activity中屬性的預設值

<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
    <!-- All customizations that are NOT specific to a particular API-level can go here. -->
    <item name="attr_one">attr one from theme</item>
    <item name="attr_two">attr two from theme</item>
    <item name="attr_three">attr three from theme</item>
    <item name="CustomizeStyle">@style/CustomizeStyleInTheme</item>
</style>

<style name="CustomizeStyleInTheme">
    <item name="attr_one">attr one from theme reference</item>
    <item name="attr_two">attr two from theme reference</item>
    <item name="attr_three">attr three from theme reference</item>
</style>

   在上述xml中,我們在AppTheme中為attr_one、attr_two與attr_three指定了值,但是同時也為CustomizeStyle指定了一個reference,而在這個reference中為attr_one、attr_two與attr_three指定了值,CustomizeStyle就是我們在attrs.xml中定義的一個屬性。在theme中為屬性設定值的方式有兩這種,直接在theme中定義或通過另一個attribute引用一個style,這兩種方式還是有區別的,在下面的獲取屬性值一節中可以看到。

三、在執行時獲取屬性值

        在styles.xml中,我們同時定義一個DefaultCustomizeStyle的style,它的作用等下可以看到。

<style name="DefaultCustomizeStyle">
    <item name="attr_one">attr one from defalut style res</item>
    <item name="attr_two">attr two from defalut style res</item>
    <item name="attr_three">attr three from defalut style res</item>
</style>

  先看下CustomTextView的程式碼

public class CustomTextView extends TextView {
    private static final String TAG = CustomTextView.class.getSimpleName();

    public CustomTextView(Context context) {
        super(context);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.CustomizeStyle);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyle, R.style.DefaultCustomizeStyle);
        String one = a.getString(R.styleable.Customize_attr_one);
        String two = a.getString(R.styleable.Customize_attr_two);
        String three = a.getString(R.styleable.Customize_attr_three);
        String four = a.getString(R.styleable.Customize_attr_four);
        Log.i(TAG, "one:" + one);
        Log.i(TAG, "two:" + two);
        Log.i(TAG, "three:" + three);
        Log.i(TAG, "four:" + four);
        a.recycle();
    }
}

   主要程式碼都在第三個函式中,這裡也沒做什麼,只是通過Context的obtainStyledAttributes獲得了前面定義的4個屬性的值並列印了出來。

View的第三個建構函式的第三個引數defStyle

  如果在Code中例項化一個View會呼叫第一個建構函式,如果在xml中定義會呼叫第二個建構函式,而第三個函式系統是不呼叫的,要由View(我們自定義的或系統預定義的View,如此處的CustomTextView和Button)顯式呼叫,比如在這裡我們在第二個建構函式中呼叫了第三個建構函式,並將R.attr.CustomizeStyle傳給了第三個引數。

  第三個引數的意義就如同它的名字所說的,是預設的Style,只是這裡沒有說清楚,這裡的預設的Style是指它在當前Application或Activity所用的Theme中的預設Style,以系統中的Button為例說明。

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);
}

   在Code中例項化View會呼叫第一個建構函式,在XML中定義會呼叫第二個建構函式,在Button的實現中都呼叫了第三個建構函式,並且defStyle的值是com.android.internal.R.attr.buttonStyle。buttonStyle是系統中定義的一個attribute,系統預設有一個Theme,比如4.0中是Theme.Holo

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

  上面是系統預設的Theme,為buttonStyle指定了一個Style

<style name="Widget.DeviceDefault.Button" parent="Widget.Holo.Button" >
</style>

<style name="Widget.Holo.Button" parent="Widget.Button">
<item name="android:background">@android:drawable/btn_default_holo_dark</item>
    <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
    <item name="android:textColor">@android:color/primary_text_holo_dark</item>
    <item name="android:minHeight">48dip</item>
    <item name="android:minWidth">64dip</item>
</style>

   這個Style定義了系統中Button的預設屬性,如background等。

  從文件中第三個建構函式的說明中也可以看到,這個建構函式的作用是View的子類提供這個類的基礎樣式

View(Context context, AttributeSet attrs, int defStyleAttr)

Perform inflation from XML and apply a class-specific base style.

  上面說的都比較抽象,還是直接看例項程式碼來的清楚明白,實驗用的程式碼上面全都貼完了,這裡直接看結果,但在這之前要先看一個函式:obtainStyledAtributes。

obtainStyledAtributes

  我們要獲取的屬性值都是通過這個函式返回的TypedArray獲得的,這是官方說明:obtainStyledAttributes,以下是函式原型:

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)

  4個引數的意思分別是:

    set:屬性值的集合

    attrs:我們要獲取的屬性的資源ID的一個數組,如同ContextProvider中請求資料庫時的Projection陣列,就是從一堆屬性中我們希望查詢什麼屬性的值

    defStyleAttr:這個是當前Theme中的一個attribute,是指向style的一個引用,當在layout xml中和style中都沒有為View指定屬性時,會從Theme中這個attribute指向的Style中查詢相應的屬性值,這就是defStyle的意思,如果沒有指定屬性值,就用這個值,所以是預設值,但這個attribute要在Theme中指定,且是指向一個Style的引用,如果這個引數傳入0表示不向Theme中搜索預設值

    defStyleRes:這個也是指向一個Style的資源ID,但是僅在defStyleAttr為0或defStyleAttr不為0但Theme中沒有為defStyleAttr屬性賦值時起作用

  連結中對這個函式說明勉強過得去,這裡簡要概括一下。對於一個屬性可以在多個地方指定它的值,如XML直接定義,style,Theme,而這些位置定義的值有一個優先順序,按優先順序從高到低依次是:

直接在XML中定義>style定義>由defStyleAttr和defStyleRes指定的預設值>直接在Theme中指定的值

  看這個關係式就比較明白了,defStyleAttr和defStyleRes在前面的引數說明中已經說了,“直接在Theme中指定的值”的意思在下面的示程式碼中可以看到。

實驗驗證

  相關的程式碼都前面都已經貼上了,不過為了方便檢視,這裡再把相關的XML一起貼一遍

main_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ad="http://schemas.android.com/apk/res/com.angeldevil.customstyle"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.angeldevil.customstyle.CustomTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ad:attr_one="attr one in xml"
        style="@style/ThroughStyle"
        android:text="@string/hello_world" />

</RelativeLayout>

attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="Customize">
        <attr name="attr_one" format="string" />
        <attr name="attr_two" format="string" />
        <attr name="attr_three" format="string" />
        <attr name="attr_four" format="string" />
    </declare-styleable>

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

</resources>

styles.xml
<resources>

    <style name="AppBaseTheme" parent="android:Theme.Light">
    </style>
    <!-- Application theme. -->
    <style name="AppTheme" parent="AppBaseTheme">
        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
        <item name="attr_one">attr one from theme</item>
        <item name="attr_two">attr two from theme</item>
        <item name="attr_three">attr three from theme</item>
        <item name="CustomizeStyle">@style/CustomizeStyleInTheme</item>
    </style>

    <style name="CustomizeStyleInTheme">
        <item name="attr_one">attr one from theme reference</item>
        <item name="attr_two">attr two from theme reference</item>
        <item name="attr_three">attr three from theme reference</item>
    </style>

    <style name="ThroughStyle">
        <item name="attr_one">attr one from style</item>
        <item name="attr_two">attr two from style</item>
    </style>
    
    <style name="DefaultCustomizeStyle">
        <item name="attr_one">attr one from defalut style res</item>
        <item name="attr_two">attr two from defalut style res</item>
        <item name="attr_three">attr three from defalut style res</item>
    </style>

</resources>

   在Code中是這樣獲取屬性的

public CustomTextView(Context context, AttributeSet attrs) {
    this(context, attrs, R.attr.CustomizeStyle);
}

public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
        
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyle,
                R.style.DefaultCustomizeStyle);
    ...
}

  CustomizeStyle是定義的一個attribute,DefaultCustomizeStyle是定義的一個style。

  從程式碼中可以看到,R.attr.CustomizeStyle就是前面提到的defStyleAttr,R.style.DefaultCustomizeStyle就是defStyleRes。

  在Styles.xml中我們為App定義了一個Theme:AppTheme,在這個Theme中直接為attr_one、attr_two、attr_three定義了屬性值,這個就是前面關係式中說的直接在Theme中指定的值。

  在AppTheme中同時定義了CustomizeStyle,引用了一個Style,在CustomTextView的建構函式中分別列印了attr_one、attr_two、attr_three、attr_four的值,所以下面我們就可以通過Log輸出驗證一下前面的關係式,如果一個attribute在多個位置都定義了值,究竟哪一個起作用。

  

  如上圖所示:

    • attr_one同時在xml、style、defStyleAttr、defStyleRes與Theme中直接定義了值,但起作用的是在xml中的定義
    • attr_two同時在style、defStyleAttr、defStyleRes與Theme中直接定義了值,但起用的是在style中的定義
    • attr_three同時在defStyleAttr、defStyleRes與Theme中直接定義了值,但起作用的是defStyleAttr
    • attr_four在defStyleRes中定義了,但結果仍是0。

  這可以說明:

1. 直接在XML中定義>style定義>由defStyleAttr定義的值>defStyleRes指定的預設值、直接在Theme中指定的值

2. defStyleAttr(即defStyle)不為0且在當前Theme中可以找到這個attribute的定義時,defStyleRes不起作用,所以attr_four雖然在defStyleRes(DefaultCustomizeStyle)中定義了,但取到的值仍為null。

  為了看一下defStyleRes的作用,1. 我們在AppTheme中加上attr_four的定義,2. 向obtainStyledAttributes的第三個引數傳入0,或者移除AppTheme中CustomizeStyle的定義,結果是一樣的

<style name="AppTheme" parent="AppBaseTheme">
    <item name="attr_one">attr one from theme</item>
    <item name="attr_two">attr two from theme</item>
    <item name="attr_three">attr three from theme</item>
    <item name="attr_four">attr four from theme</item>
</style>

   由於defStyleAttr為0,或者defStyleAttr不為0但是我們沒有為這個屬性賦值,所以defStyleRes起作用,當一個屬性沒有在XML和Style中賦值時,系統會在defStyleRes(此處為DefaultCustomizeStyle)查詢屬性的值。

  

  我們看到attr_three的值來自defStyleRes,而attr_four的值來自Theme中的直接定義(DefaultCustomizeStyle定義了attr_one、atrr_two、attr_three) ,這就說明

1. defStyleAtrtr即defStyle為0或Theme中沒有定義defStyle時defStyleRes才起作用

2. defStyleRes>在Theme中直接定義

 結論

   從前面的說明可以得到以下結論:

    1. 要為自定義View自定義屬性,可以通過declare-styleable宣告需要的屬性
    2. 為了在Theme設定View的預設樣式,可以同時定義一個format為reference的屬性att_a,在定義Theme時為這個attribute指定一個Style,並且在View的第二個建構函式中將R.attr.attr_a 作為第三個引數呼叫第三個建構函式
    3. 可以定義一個Style作為Theme中沒有定義attr_a時View屬性的預設值。
    4. 可以在Theme中直接為屬性賦值,但優先順序最低
    5. 當defStyleAttr(即View的建構函式的第三個引數)不為0且在Theme中有為這個attr賦值時,defStyleRes(通過obtainStyledAttributes的第四個引數指定)不起作用
    6. 屬性值定義的優先順序:xml>style>Theme中的預設Sytle>預設Style(通過obtainStyledAttributes的第四個引數指定)>在Theme中直接指定屬性值

相關推薦

Android定義樣式View建構函式引數defStyle意義

零、序 零、序   系統自帶的View可以在xml中配置屬性,對於寫的好的Custom View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置,需要以下4個步驟: 通過<declare-styleable>為自定

電路Multisim基礎 交流電源的引數意義

       慈心積善融學習,技術願為有情學。善心速造多好事,前人栽樹後乘涼。我今於此寫經驗,願見文者得啟發。 60HZ是頻率,1/f=T 在暫停時,檢視示波器的波形。 示波器的兩個測量的

android定義控制元件View在Activity使用findByViewId得到結果為null,解決方法。。

androidの自定義控制元件View在Activity中使用findByViewId得到結果為null 1.  大家常常自定義view,,然後在xml 中新增該view 元件。。如果在Activity 中使用findByViewId 方法獲取該view 時候,返回物件總為

Android RatingBar 定義樣式

android style Android RatingBar 自定義樣式1.先定義Style:<style name="RadingStyle" parent="@android:style/Widget.RatingBar"> <!-- 定義星星圖片 -->

Python定義異常丟擲異常

# class ShortInputException(Exception): # def __init__(self,length,atleast): # super().__init__() # self.length = length #

解決定義控制元件View在MainActivityfindviewbyid為空的問題

同事在自定義轉盤的程式碼里加了一個介面回撥,一直崩潰,一直以為是介面的問題 ,後來才發現是view中建構函式的問題 public Lucky(Context context) { this(context,null); } public Lucky(

BIGEMAP教您如何在ArcGIS定義座標系投影轉換

  座標系統是GIS資料重要的數學基礎,用於表示地理要素、影象和觀測結果的參照系統,座標系統的定義能夠保證地理資料在軟體中正確的顯示其位置、方向和距離,缺少座標系統的GIS資料是不完善的,因此在ArcGIS軟體中正確的定義座標系統以及進行投影轉換的操作非常重要。 1.&

android EditText定義樣式

ber ins gre roi osc 只需要 auto source mach 1.去掉邊框 EditText的background屬性設置為@null就搞定了:android:background="@null" style屬性倒是可加可不加 附原文: @Sl

android SeekBar定義樣式滑動條的使用

seekbar是android常用的一款手動滑動和自動滑動的滑動條控制元件,可以作為手動選擇數值的控制元件,也可作為進度條來使用,下面來介紹seekbar作為進度條的常用配置 一、樣式設定,在xml佈局檔案中引入下面的程式碼 <SeekBar

Android ProgressBar 定義樣式(六),模擬QQ pad版載入(位置居中)

讓ProgressBar居於真個螢幕在正中間: xml佈局檔案如下: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://s

android ActionBar定義樣式

ActionBar actionBar = getActionBar(); //只新增CustomView到ActionBar actionBar.setDisplayShowCustomEnabled(true); View view = getLayoutInflat

Qt MainWindow定義函數調用MainWindow方法

main mes ren col append tex char nbsp set MainWindow *m;//定義全局 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),

Java定義物件使用Collections工具類的Sort方法

Collections工具類中的sort方法有兩種形式: (1) sort(List<T> list) (2) sort(List<T> list, Comparator<? super T> c) 第一種方法中List型別的物件必須實現Comparable介面,此外,

在Excel定義四捨六入函式Round46()

'================================ ' 四捨六入函式 ' ' 演算法:四合六入五考慮,五後非零就進一,五後皆零看奇偶,五前為偶應捨去,五前為奇要進一。 ' 作者:崔軍明 ' 編寫:2011-1-14 ' 修改:2011-1-15 '=======================

Fragment強烈不推薦使用定義帶參的建構函式

專案在執行monkey63小時左右,出現9次CRASH: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.aliyun.easylauncher/com.aliyun.easyla

空指標在主函式被賦值在呼叫函式被賦值的差別

# include <stdio.h> int main() { int a=10; int *p=&a; p=NULL; printf("%d\n",*p); return 0; } 在這個程式中因為將a的地址賦值給指標p

Android定義Dialog樣式

public class MyMiddleDialog extends Dialog { private Context context; public MyMiddleDialog(Context context) { sup

Android定義ProgressBar的樣式

如果想快速獲取水平進度條顯示操作,直接進入第四步和第六步操作就可以了!! 首先可以去sdk中檢視 sdk1\platforms\android-23\data\res\values,中的sty

Android定義SeekBar的樣式

有時候原生的SeekBar太醜了,已經滿足不了我們的效果,需要我們自定義樣式。 第一步:在drawable裡建立一個xml檔案 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://

Android定義View的研究 -- 在XML引用定義View

如果在一直使用SetContentView(new HellwView(this)覺得總是少了一點東西,少了什麼了,失去了Android中使用XML定義元件的便攜性,這種感覺讓人很不爽,呵呵,在這節裡我們會看到一個自定義View報錯的解決方法,讓我們來看看在XML中定義Vi