前言:人總會(huì)疲憊弹沽,當(dāng)感覺累的時(shí)候窜锯,再堅(jiān)持一下熏挎,再堅(jiān)持一下就好蚌成。
1.初始Custom View的構(gòu)造函數(shù)
通常我們?cè)趯?shí)現(xiàn)Custom View的時(shí)候,都會(huì)先繼承View并實(shí)現(xiàn)View的三個(gè)構(gòu)造函數(shù),例如:
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
public class MyCustomView extends View {
/**
* 第一個(gè)構(gòu)造函數(shù)
*/
public MyCustomView(Context context) {
this(context, null);
}
/**
* 第二個(gè)構(gòu)造函數(shù)
*/
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 第三個(gè)構(gòu)造函數(shù)
*/
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO:獲取自定義屬性
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
1:在代碼中直接new一個(gè)Custom View實(shí)例的時(shí)候,會(huì)調(diào)用第一個(gè)構(gòu)造函數(shù).
2:在xml布局文件中調(diào)用Custom View的時(shí)候,確切的說是xml解析到當(dāng)前的標(biāo)簽的時(shí)候前痘,會(huì)調(diào)用第二個(gè)構(gòu)造函數(shù).
3:在xml布局文件中調(diào)用Custom View,并且Custom View標(biāo)簽中還有自定義屬性時(shí),這里調(diào)用的還是第二個(gè)構(gòu)造函數(shù).
也就是說,系統(tǒng)默認(rèn)只會(huì)調(diào)用Custom View的前兩個(gè)構(gòu)造函數(shù),至于第三個(gè)構(gòu)造函數(shù)的調(diào)用,通常是我們自己在構(gòu)造函數(shù)中主動(dòng)調(diào)用的(例如,在第二個(gè)構(gòu)造函數(shù)中調(diào)用第三個(gè)構(gòu)造函數(shù))
接下來就針對(duì)自定義View的屬性展開:
2.為什么要自定義屬性
要使用屬性凛捏,首先這個(gè)屬性應(yīng)該存在,所以如果我們要使用自己的屬性芹缔,必須要先把他定義出來才能使用坯癣。但我們平時(shí)在寫布局文件的時(shí)候好像沒有自己定義屬性,但我們照樣可以用很多屬性最欠,這是為什么坡锡?我想大家應(yīng)該都知道:系統(tǒng)定義好的屬性我們就可以拿來用唄,但是你們知道系統(tǒng)定義了哪些屬性嗎窒所?哪些屬性是我們自定義控件可以直接使用的,哪些不能使用帆锋?什么樣的屬性我們能使用吵取?這些問題我想大家不一定都弄得清除,下面我們?nèi)ヒ灰唤忾_這些謎團(tuán)锯厢。
??系統(tǒng)定義的所有屬性我們可以在\sdk\platforms\android-xx\data\res\values目錄下找到attrs.xml這個(gè)文件皮官,這就是系統(tǒng)自帶的所有屬性,打開看看一些比較熟悉的:
<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文件中的屬性实辑,發(fā)現(xiàn)他們都是有規(guī)律的分組的形式組織的捺氢。以declare-styleable 為一個(gè)組合,后面有一個(gè)name屬性剪撬,屬性的值為View 摄乒、TextView 等等,有沒有想到什么残黑?沒錯(cuò)馍佑,屬性值為View的那一組就是為View定義的屬性,屬性值為TextView的就是為TextView定義的屬性…梨水。
因?yàn)樗械目丶际荲iew的子類拭荤,所以為View定義的屬性所有的控件都能使用,這就是為什么我們的自定義控件沒有定義屬性就能使用一些系統(tǒng)屬性疫诽。
但是并不是每個(gè)控件都能使用所有屬性舅世,比如TextView是View的子類,所以為View定義的所有屬性它都能使用奇徒,但是子類肯定有自己特有的屬性雏亚,得單獨(dú)為它擴(kuò)展一些屬性,而單獨(dú)擴(kuò)展的這些屬性只有它自己能有逼龟,View是不能使用的评凝,比如View中不能使用android:text=“”。又比如腺律,LinearLayout中能使用layout_weight屬性奕短,而RelativeLayout卻不能使用宜肉,因?yàn)閘ayout_weight是為LinearLayout的LayoutParams定義的。
綜上所述翎碑,自定義控件如果不自定義屬性谬返,就只能使用VIew的屬性,但為了給我們的控件擴(kuò)展一些屬性日杈,我們就必須自己去定義遣铝。
2. 怎樣自定義屬性
自定義屬性的步驟想必大家都所了解:
1、自定義一個(gè)CustomView(extends View )類
2莉擒、編寫values/attrs.xml酿炸,在其中編寫styleable和item等標(biāo)簽元素
3、在布局文件中CustomView使用自定義的屬性(注意命名空間)
4涨冀、在CustomView的構(gòu)造方法中通過TypedArray獲取
有幾個(gè)問題不知道是否知道:
以上步驟是如何奏效的填硕?
styleable 的含義是什么?可以不寫嘛鹿鳖?我自定義屬性扁眯,我聲明屬性就好了,為什么一定要寫個(gè)styleable呢翅帜?
如果系統(tǒng)中已經(jīng)有了語義比較明確的屬性姻檀,我可以直接使用嘛?
構(gòu)造方法中的有個(gè)參數(shù)叫做AttributeSet
(eg: MyTextView(Context context, AttributeSet attrs) )這個(gè)參數(shù)看名字就知道包含的是參數(shù)的數(shù)組涝滴,那么我能不能通過它去獲取我的自定義屬性呢绣版?-
TypedArray是什么鬼?從哪冒出來的歼疮,就要我去使用僵娃?
看完本篇文章,相信你會(huì)徹底解決上述問題腋妙。
翻閱系統(tǒng)的屬性文件默怨,你會(huì)發(fā)現(xiàn),有兩種獲取形式(下面講)骤素;這兩種
區(qū)別就是attr標(biāo)簽后面帶不帶format屬性匙睹,如果帶format的就是在定義屬性,如果不帶format的就是在使用已有的屬性济竹,name的值就是屬性的名字痕檬,format是限定當(dāng)前定義的屬性能接受什么值。打個(gè)比方送浊,比如系統(tǒng)已經(jīng)定義了android:text屬性梦谜,我們的自定義控件也需要一個(gè)文本的屬性,可以有兩種方式:
第一種:我們并不知道系統(tǒng)定義了此名稱的屬性,我們自己定義一個(gè)名為text或者mText的屬性(屬性名稱可以隨便起的)
<resources>
<declare-styleable name="MyTextView">
<attr name=“text" format="string" />
</declare-styleable>
</resources>
第二種:我們知道系統(tǒng)已經(jī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屬性米奸。
4. 屬性值的類型format
format支持的類型一共有11種:
(1). reference:參考某一資源ID
- 屬性定義:
<declare-styleable name = "名稱">
<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"/>
(1). reference:參考某一資源ID
- 屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference" />
</declare-styleable>
- 屬性使用:
<ImageView android:background = "@drawable/圖片ID"/>
(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="名稱">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
- 屬性使用:
<LinearLayout
android:orientation = "vertical">
</LinearLayout>
注意:枚舉類型的屬性在使用的過程中只能同時(shí)使用其中一個(gè)昼接,不能 android:orientation = “horizontal|vertical"
(10). flag:位或運(yùn)算
- 屬性定義:
<declare-styleable name="名稱">
<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)算類型的屬性在使用的過程中可以使用多個(gè)值
(11). 混合類型:屬性定義時(shí)可以指定多種類型值
- 屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference|color" />
</declare-styleable>
- 屬性使用:
<ImageView
android:background = "@drawable/圖片ID" />
或者:
<ImageView
android:background = "#00FF00" />
通過上面的學(xué)習(xí)我們已經(jīng)知道怎么定義各種類型的屬性,以及怎么使用它們悴晰,但是我們寫好布局文件之后辩棒,要在控件中使用這些屬性還需要將它解析出來。
5. 類中獲取屬性值
在這之前膨疏,順帶講一下命名空間,我們?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)中查找該屬性來源。只有引入了命名空間窘俺,XML文件才知道下面使用的屬性應(yīng)該去哪里找(哪里定義的饲帅,不能憑空出現(xiàn),要有根據(jù))瘤泪。
如果我們自定義屬性灶泵,這個(gè)屬性應(yīng)該去我們的應(yīng)用程序包中找,所以要引入我們應(yīng)用包的命名空間xmlns:itydl="http://schemas.android.com/apk/res-auto”
对途,res-auto表示自動(dòng)查找赦邻,還有一種寫法xmlns:itydl="http://schemas.android.com/apk/com.example.openxu.
myview",com.example.itydl.myview為我們的應(yīng)用程序包名实檀。
按照上面學(xué)習(xí)的知識(shí)惶洲,我們先定義一些屬性,并寫好布局文件膳犹。
先在res\values目錄下創(chuàng)建attrs.xml恬吕,定義自己的屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<!--聲明MyTextView需要使用系統(tǒng)定義過的text屬性,注意前面需要加上android命名-->
<attr name="android:text" />
<attr name="mTextColor" format="color" />
<attr name="mTextSize" format="dimension" />
</declare-styleable>
</resources>
注意,上面styleable的name寫的是MyTextView须床,這個(gè)其實(shí)不一定要寫自定義View的類名铐料,其他的字母也是可以的。只不過規(guī)范化書寫一般會(huì)寫上針對(duì)哪個(gè)類的類名稱
在布局文件中,使用屬性(注意引入我們應(yīng)用程序的命名空間钠惩,這樣在能找到我們包中的attrs):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:itydl="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.itydl.myview.MyTextView
android:layout_width="200dip"
android:layout_height="100dip"
itydl:mTextSize="25sp"
android:text="我是文字"
itydl: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("itydl", “text屬性值:"+mText);
Log.v("itydl", "mTextColor屬性值:"+mTextColor);
Log.v("itydl", "mTextSize屬性值:"+mTextSize);
}
log輸出:
6. Attributeset和TypedArray以及declare-styleable
Attributeset看名字就知道是一個(gè)屬性的集合柒凉,實(shí)際上,它內(nèi)部就是一個(gè)XML解析器妻柒,幫我們將布局文件中該控件的所有屬性解析出來扛拨,并以key-value的兼職對(duì)形式維護(hù)起來。其實(shí)我們完全可以只用他通過下面的代碼來獲取我們的屬性就行举塔。
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輸出:
發(fā)現(xiàn)通過Attributeset獲取屬性的值時(shí)绑警,它將我們布局文件中的值原原本本的獲取出來的,比如寬度200.0dip央渣,其實(shí)這并不是我們想要的计盒,如果我們接下來要使用寬度值,我們還需要將dip去掉芽丹,然后轉(zhuǎn)換成整形北启,這多麻煩。其實(shí)這都不算什么拔第,更惡心的是咕村,backgroud我應(yīng)用了一個(gè)color資源ID,它直接給我拿到了這個(gè)ID值蚊俺,前面還加了個(gè)@懈涛,接下來我要自己獲取資源,并通過這個(gè)ID值獲取到真正的顏色泳猬。
我們?cè)贀QTypedArray試試批钠。
使用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("itydl", "width:"+width);
Log.v("itydl", "hight:"+hight);
Log.v("itydl", "backgroud:"+backgroud);
Log.v("itydl", "mText:"+mText);
Log.v("itydl", "mTextColor:"+mTextColor);
Log.v("itydl", "mTextSize:"+mTextSize);ext, 0, mText.length(), mBound);
}
log輸出:
看看多么舒服的結(jié)果,我們得到了想要的寬高(float型)得封,背景顏色(color的十進(jìn)制)等埋心,TypedArray提供了一系列獲取不同類型屬性的方法,這樣就可以直接得到我們想要的數(shù)據(jù)類型忙上,而不用像Attributeset獲取屬性后還要一個(gè)個(gè)處理才能得到具體的數(shù)據(jù)拷呆,實(shí)際上TypedArray是為我們獲取屬性值提供了方便,注意一點(diǎn)疫粥,TypedArray使用完畢后記得調(diào)用 ta.recycle();回收 洋腮。
7、快速獲取各屬性的方式:
你可能還見過如下方式自定義屬性的文章手形,其實(shí)接下來要說的就是一種快速獲取自定義屬性的方式:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
<declare-styleable name="CustomTitleView">
<attr name="titleText" />
<attr name="titleTextColor" />
<attr name="titleTextSize" />
</declare-styleable>
</resources>
然后在布局中聲明我們的自定義View
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.customview01.view.CustomTitleView
android:layout_width="200dp"
android:layout_height="100dp"
custom:titleText="3712"
custom:titleTextColor="#ff0000"
custom:titleTextSize="40sp" />
</RelativeLayout>
在View的構(gòu)造方法中啥供,獲得我們的自定義的樣式
/**
* 獲得我們所定義的自定義樣式屬性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.CustomTitleView_titleText:
mTitleText = a.getString(attr);
break;
case R.styleable.CustomTitleView_titleTextColor:
// 默認(rèn)顏色設(shè)置為黑色
mTitleTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTitleView_titleTextSize:
// 默認(rèn)設(shè)置為16sp,TypeValue也可以把sp轉(zhuǎn)化為px
mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
這種方式在實(shí)際開發(fā)中也是最常見的方式库糠。
8.declare-styleable
他是用來干嘛的伙狐,如果不要它可不可以涮毫?
直接給答案吧。答案是可以的贷屎,我們自定義屬性完全可以寫成下面的形式:
<?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è)整型常亮罢防,我們可以通過這個(gè)ID值找到資源文件。
??屬性在R中對(duì)應(yīng)的類是public static final class attr
唉侄,如果我們寫了declare-styleable
咒吐,在R文件中就會(huì)生成styleable類,這個(gè)類其實(shí)就是將每個(gè)控件的屬性分組属划,然后記錄屬性的索引值恬叹,而TypedArray正好需要通過此索引值獲取屬性。
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;
}
記住了嗎同眯?如果不要它也是可以噠绽昼。
相信看完文章,自定義屬性相關(guān)的知識(shí)已經(jīng)難不住你了~當(dāng)然這也是自定義View必須掌握的基礎(chǔ)知識(shí)须蜗。
未完待續(xù)