本篇重點(diǎn)講下自定義屬性,當(dāng)然在進(jìn)行自定義屬性前你還得寫(xiě)至少寫(xiě)2個(gè)構(gòu)造函數(shù):
public MyView(Context context) {
super(context);
init();//初始化
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();//初始化
}
繼承view重寫(xiě)構(gòu)造此處就不在多說(shuō)了想詳細(xì)了解的可以參考下面文章:
android view構(gòu)造函數(shù)研究
Android中自定義樣式與View的構(gòu)造函數(shù)中的第三個(gè)參數(shù)defStyle的意義
為什么要自定義屬性
要使用屬性柴灯,首先要有這個(gè)屬性,所以如果我們要使用自己的屬性甫恩,必須要先把他定義出來(lái)才能使用。但我們平時(shí)在寫(xiě)布局文件的時(shí)候好像沒(méi)有自己定義屬性榛斯,但我們照樣可以用很多屬性,這是因?yàn)橄到y(tǒng)幫我們定義好了這些屬性,我們可以直接用搂捧,但是系統(tǒng)定義了哪些屬性嗎驮俗?哪些屬性是我們自定義控件可以直接使用的,哪些不能使用允跑?什么樣的屬性我們能使用王凑?下面我們?nèi)タ聪孪到y(tǒng)定義的所有屬性,系統(tǒng)屬性我們可以在\sdk\platforms\android-XX\data\res\values目錄下找到attrs.xml這個(gè)文件,這就是系統(tǒng)自帶的所有屬性聋丝,打開(kāi)看看一些比較熟悉的:
<declare-styleable name="View">
<attr name="id" format="reference" />
<attr name="background" format="reference|color" />
<attr name="padding" format="dimension" />
...
<attr name="focusable" format="boolean" />
...
</declare-styleable>
<declare-styleable name="TextView">
<attr name="text" format="string" localization="suggested" />
<attr name="hint" format="string" />
<attr name="textColor" />
<attr name="textColorHighlight" />
<attr name="textColorHint" />
...
</declare-styleable>
<declare-styleable name="ViewGroup_Layout">
<attr name="layout_width" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
<attr name="layout_height" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
</declare-styleable>
<declare-styleable name="LinearLayout_Layout">
<attr name="layout_width" />
<attr name="layout_height" />
<attr name="layout_weight" format="float" />
<attr name="layout_gravity" />
</declare-styleable>
<declare-styleable name="RelativeLayout_Layout">
<attr name="layout_centerInParent" format="boolean" />
<attr name="layout_centerHorizontal" format="boolean" />
<attr name="layout_centerVertical" format="boolean" />
...
</declare-styleable>
看看上面attrs.xml文件中的屬性索烹,都是有規(guī)律的。以declare-styleable 為一個(gè)組合潮针,后面有一個(gè)name屬性术荤,屬性的值為View 、TextView 等等每篷,有沒(méi)有想到什么瓣戚?沒(méi)錯(cuò),屬性值為View的那一組就是為View定義的屬性,屬性值為T(mén)extView的就是為T(mén)extView定義的屬性。
??因?yàn)樗械目丶际荲iew的子類(lèi)襟交,所以View定義的屬性所有的控件都能使用轻猖,這就是為什么我們的自定義控件沒(méi)有定義屬性就能使用一些系統(tǒng)屬性燃领。
??但有些控件的特有屬性并不是每個(gè)控件都能使用,比如TextView是View的子類(lèi),View定義的所有屬性它都能使用,但是子類(lèi)肯定有自己特有的屬性,比如android:text=“”仓技。TextView中的text屬性L(fǎng)inearLayout就沒(méi)法使用。
??綜上所述俗他,自定義控件如果不自定義屬性脖捻,就只能使用View的屬性,但為了給我們的控件擴(kuò)展一些屬性兆衅,我們就必須自己去定義地沮。
怎樣自定義屬性
翻閱系統(tǒng)的屬性文件,你會(huì)發(fā)現(xiàn)attr標(biāo)簽后面有的帶format屬性有的不帶format屬性羡亩,如果帶format的就是在定義屬性摩疑,如果不帶format的就是在使用已有的屬性,其中name的值就是屬性的名字畏铆,format是限定當(dāng)前定義的屬性能接受什么值雷袋。
打個(gè)比方,比如系統(tǒng)已經(jīng)定義了android:text屬性辞居,我們的自定義控件也需要一個(gè)文本的屬性片排,可以有兩種方式:
第一種:我們并不知道系統(tǒng)定義了此名稱(chēng)的屬性寨腔,我們自己定義一個(gè)名為text或者mText的屬性(屬性名稱(chēng)可以隨便起的)
<resources>
<declare-styleable name="MyTextView">
<attr name=“text" format="string" />
</declare-styleable>
</resources>
第二種:我們知道系統(tǒng)已經(jīng)定義過(guò)名稱(chēng)為text的屬性,我們不用自己定義率寡,只需要在自定義屬性中申明,我要使用這個(gè)text屬性
(注意加上android命名空間倚搬,這樣才知道使用的是系統(tǒng)的text屬性)
<resources>
<declare-styleable name="MyTextView">
<attr name=“android:text"/>
</declare-styleable>
</resources>
為什么系統(tǒng)定義了此屬性冶共,我們?cè)谑褂玫臅r(shí)候還要聲明?因?yàn)槊拷纾到y(tǒng)定義的text屬性是給TextView使用的捅僵,如果我們不申明,就不能使用text屬性眨层。
屬性值的類(lèi)型format
format支持的類(lèi)型一共有11種:
- (1). reference:參考某一資源ID
屬性定義:
<declare-styleable name = "名稱(chēng)">
<attr name = "background" format = "reference" />
</declare-styleable>
屬性使用:
<ImageView android:background = "@drawable/圖片ID"/>
- (2). color:顏色值
屬性定義:
<attr name = "textColor" format = "color" />
屬性使用:
<TextView android:textColor = "#00FF00" />
- (3). boolean:布爾值
屬性定義:
<attr name = "focusable" format = "boolean" />
屬性使用:
<Button android:focusable = "true"/>
- (4). dimension:尺寸值**
屬性定義:
<attr name = "layout_width" format = "dimension" />
屬性使用:
<Button android:layout_width = "42dip"/>
- (5). float:浮點(diǎn)值
屬性定義:
<attr name = "fromAlpha" format = "float" />
屬性使用:
<alpha android:fromAlpha = "1.0"/>
- (6). integer:整型值**
屬性定義:
<attr name = "framesCount" format="integer" />
屬性使用:
`<animated-rotate android:framesCount = "12"/ - (7). string:字符串
屬性定義:
<attr name = "text" format = "string" />
屬性使用:
<TextView android:text = "我是文本"/>
- (8). fraction:百分?jǐn)?shù)**
屬性定義:
<attr name = "pivotX" format = "fraction" />
屬性使用:
<rotate android:pivotX = "200%"/>
- (9). enum:枚舉值
屬性定義:
<declare-styleable name="名稱(chēng)">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
屬性使用:
<LinearLayout android:orientation = "vertical"></LinearLayout>
注意:枚舉類(lèi)型的屬性在使用的過(guò)程中只能同時(shí)使用其中一個(gè)庙楚,不能 android:orientation = “horizontal|vertical"
- (10). flag:位或運(yùn)算
屬性定義:
<declare-styleable name="名稱(chēng)">
<attr name="gravity">
<flag name="top" value="0x30" />
<flag name="bottom" value="0x50" />
<flag name="left" value="0x03" />
<flag name="right" value="0x05" />
<flag name="center_vertical" value="0x10" />
</attr>
</declare-styleable>
屬性使用:
<TextView android:gravity="bottom|left"/>
注意:位運(yùn)算類(lèi)型的屬性在使用的過(guò)程中可以使用多個(gè)值
- (11). 混合類(lèi)型:屬性定義時(shí)可以指定多種類(lèi)型值
屬性定義:
<declare-styleable name = "名稱(chēng)">
<attr name = "background" format = "reference|color" />
</declare-styleable>
屬性使用:
<ImageViewandroid:background = "@drawable/圖片ID" />
或者:
<ImageViewandroid:background = "#00FF00" />
??通過(guò)上面的學(xué)習(xí)我們已經(jīng)知道怎么定義各種類(lèi)型的屬性,以及怎么使用它們趴樱,但是我們寫(xiě)好布局文件之后馒闷,要在控件中使用這些屬性還需要將它解析出來(lái)。
類(lèi)中獲取屬性值
在這之前叁征,順帶講一下命名空間纳账,我們?cè)诓季治募惺褂脤傩缘臅r(shí)候android:layout_width="match_parent"
發(fā)現(xiàn)前面都帶有一個(gè)android:,這個(gè)android就是上面引入的命名空間xmlns:android="http://schemas.android.com/apk/res/android”
捺疼,表示到android系統(tǒng)中查找該屬性來(lái)源疏虫。只有引入了命名空間,XML文件才知道下面使用的屬性應(yīng)該去哪里找(哪里定義的啤呼,不能憑空出現(xiàn)卧秘,要有根據(jù))。
??如果我們自定義屬性官扣,這個(gè)屬性應(yīng)該去我們的應(yīng)用程序包中找翅敌,所以要引入我們應(yīng)用包的命名空間xmlns:openxu="http://schemas.android.com/apk/res-auto”
,res-auto表示自動(dòng)查找醇锚,還有一種寫(xiě)法xmlns:openxu="http://schemas.android.com/apk/com.example.openxu.myview"
哼御,com.example.openxu.myview為我們的應(yīng)用程序包名。
??按照上面學(xué)習(xí)的知識(shí)焊唬,我們先定義一些屬性恋昼,并寫(xiě)好布局文件。 先在res\values目錄下創(chuàng)建attrs.xml赶促,定義自己的屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<!--聲明MyTextView需要使用系統(tǒng)定義過(guò)的text屬性,注意前面需要加上android命名-->
<attr name="android:text" />
<attr name="mTextColor" format="color" />
<attr name="mTextSize" format="dimension" />
</declare-styleable>
</resources>
在布局文件中液肌,使用屬性(注意引入我們應(yīng)用程序的命名空間,這樣在能找到我們包中的attrs):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:openxu="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.openxu.myview.MyTextView
android:layout_width="200dip"
android:layout_height="100dip"
openxu:mTextSize="25sp"
android:text="我是文字"
openxu:mTextColor ="#0000ff"
android:background="#ff0000"/>
</LinearLayout>
在構(gòu)造方法中獲取屬性值:
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
String text = ta.getString(R.styleable.MyTextView_android_text);
int mTextColor = ta.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
int mTextSize = ta.getDimensionPixelSize(R.styleable.MyTextView_mTextSize, 100);
ta.recycle(); //注意回收
Log.v("openxu","text屬性值:"+mText);
Log.v("openxu", "mTextColor屬性值:"+mTextColor);
Log.v("openxu", "mTextSize屬性值:"+mTextSize);
}
log輸出:
V/openxu(25652): mText屬性值:我是文字
V/openxu(25652): mTextColor屬性值:-16776961
V/openxu(25652): mTextSize屬性值:75
到此為止,屬性的定義我們應(yīng)該學(xué)的差不多了鸥滨,但有沒(méi)有發(fā)現(xiàn)構(gòu)造方法中獲取屬性值的時(shí)候有兩個(gè)比較陌生的類(lèi)AttributeSet和TypedArray嗦哆,這兩個(gè)類(lèi)是怎么把屬性值從布局文件中解析出來(lái)的谤祖?
Attributeset和TypedArray以及declare-styleable
Attributeset看名字就知道是一個(gè)屬性的集合,實(shí)際上老速,它內(nèi)部就是一個(gè)XML解析器粥喜,幫我們將布局文件中該控件的所有屬性解析出來(lái),并以key-value的鍵值對(duì)形式維護(hù)起來(lái)橘券。其實(shí)我們完全可以只用他通過(guò)下面的代碼來(lái)獲取我們的屬性就行额湘。
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);
String attrVal = attrs.getAttributeValue(i);
Log.e("openxu", "attrName = " + attrName + " , attrVal = " + attrVal);
}
}
log輸出:
E/openxu(14704): attrName = background , attrVal = @2131427347
E/openxu(14704): attrName = layout_width , attrVal = 200.0dip
E/openxu(14704): attrName = layout_height , attrVal = 100.0dip
E/openxu(14704): attrName = text , attrVal = 我是文字
E/openxu(14704): attrName = mTextSize , attrVal = 25sp
E/openxu(14704): attrName = mTextColor , attrVal = #0000ff
發(fā)現(xiàn)通過(guò)Attributeset獲取屬性的值時(shí),它將我們布局文件中的值原原本本的獲取出來(lái)的旁舰,比如寬度200.0dip锋华,其實(shí)這并不是我們想要的,如果我們接下來(lái)要使用寬度值箭窜,我們還需要將dip去掉毯焕,然后轉(zhuǎn)換成整形,這多麻煩磺樱。其實(shí)這都不算什么纳猫,更惡心的是,backgroud我應(yīng)用了一個(gè)color資源ID坊罢,它直接給我拿到了這個(gè)ID值续担,前面還加了個(gè)@,接下來(lái)我要自己獲取資源活孩,并通過(guò)這個(gè)ID值獲取到真正的顏色物遇。
??在這里,穿插一個(gè)知識(shí)點(diǎn)憾儒,定義屬性的時(shí)候有一個(gè)declare-styleable询兴,他是用來(lái)干嘛的,如果不要它可不可以起趾?答案是可以的诗舰,我們自定義屬性完全可以寫(xiě)成下面的形式:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="mTextColor" format="color" />
<attr name="mTextSize" format="dimension" />
</resources>
之前的形式是這樣的:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<attr name="android:text" />
<attr name="android:layout_width" />
<attr name="android:layout_height" />
<attr name="android:background" />
<attr name="mTextColor" format="color" />
<attr name="mTextSize" format="dimension" />
</declare-styleable>
</resources>
或者:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--定義屬性-->
<attr name="mTextColor" format="color" />
<attr name="mTextSize" format="dimension" />
<declare-styleable name="MyTextView">
<!--生成索引-->
<attr name="android:text" />
<attr name="android:layout_width" />
<attr name="android:layout_height" />
<attr name="android:background" />
<attr name="mTextColor" />
<attr name="mTextSize" />
</declare-styleable>
</resources>
我們都知道所有的資源文件在R中都會(huì)對(duì)應(yīng)一個(gè)整型常亮,我們可以通過(guò)這個(gè)ID值找到資源文件训裆。
??屬性在R中對(duì)應(yīng)的類(lèi)是public static final class attr眶根,如果我們寫(xiě)了declare-styleable,在R文件中就會(huì)生成styleable類(lèi)边琉,這個(gè)類(lèi)其實(shí)就是將每個(gè)控件的屬性分組属百,然后記錄屬性的索引值,而TypedArray正好需要通過(guò)此索引值獲取屬性变姨。
public static final class styleable
public static final int[] MyTextView = {
0x0101014f, 0x7f010038, 0x7f010039
};
public static final int MyTextView_android_text = 0;
public static final int MyTextView_mTextColor = 1;
public static final int MyTextView_mTextSize = 2;
}
使用TypedArray獲取屬性值:
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
String mText = ta.getString(R.styleable.MyTextView_android_text);
int mTextColor = ta.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
int mTextSize = ta.getDimensionPixelSize(R.styleable.MyTextView_mTextSize, 100);
float width = ta.getDimension(R.styleable.MyTextView_android_layout_width, 0.0f);
float hight = ta.getDimension(R.styleable.MyTextView_android_layout_height,0.0f);
int backgroud = ta.getColor(R.styleable.MyTextView_android_background, Color.BLACK);
ta.recycle(); //注意回收
Log.v("openxu", "width:"+width);
Log.v("openxu", "hight:"+hight);
Log.v("openxu", "backgroud:"+backgroud);
Log.v("openxu", "mText:"+mText);
Log.v("openxu", "mTextColor:"+mTextColor);
Log.v("openxu", "mTextSize:"+mTextSize);ext, 0, mText.length(), mBound);
}
log輸出:
V/openxu(22630): width:600.0
V/openxu(22630): hight:300.0
V/openxu(22630): backgroud:-12627531
V/openxu(22630): mText:我是文字
V/openxu(22630): mTextColor:-16777216
V/openxu(22630): mTextSize:100
看看多么舒服的結(jié)果族扰,我們得到了想要的寬高(float型),背景顏色(color的十進(jìn)制)等,TypedArray提供了一系列獲取不同類(lèi)型屬性的方法渔呵,這樣就可以直接得到我們想要的數(shù)據(jù)類(lèi)型怒竿,而不用像Attributeset獲取屬性后還要一個(gè)個(gè)處理才能得到具體的數(shù)據(jù),實(shí)際上TypedArray是為我們獲取屬性值提供了方便扩氢,注意一點(diǎn)耕驰,TypedArray使用完畢后記得調(diào)用 ta.recycle();回收 。