Android已經(jīng)為我們提供了大量的View供我們使用兽赁,但是可能有時(shí)候這些組件不能滿足我們的開發(fā)中的需求蔑滓,這時(shí)候就需要自定義控件了渔欢。自定義控件對(duì)于初學(xué)者總是感覺是一種復(fù)雜的技術(shù)匿乃。因?yàn)槔锩嫔婕暗降闹R(shí)點(diǎn)會(huì)比較多拟枚。但是任何復(fù)雜的技術(shù)后面都是一點(diǎn)點(diǎn)簡(jiǎn)單知識(shí)的積累。通過對(duì)自定義控件的學(xué)習(xí)去可以更深入的掌握android的相關(guān)知識(shí)點(diǎn)吼渡,所以學(xué)習(xí)android自定義控件是很有必要的容为。記得以前學(xué)習(xí)總是想著去先理解很多知識(shí)點(diǎn),然后再來學(xué)著自定義控件寺酪,但是每次寫自定義控件的時(shí)候總是不知道從哪里下手啊坎背。后來在學(xué)習(xí)的過程中發(fā)現(xiàn)自己跟著去寫一些簡(jiǎn)單的自定義控件泛豪,然后在這個(gè)過程中遇到了沒有掌握的知識(shí)點(diǎn)再去學(xué)習(xí)姑隅。不僅自定義控件的能力有所提高。其它的知識(shí)也有了很好的鞏固和認(rèn)識(shí)俯艰。
1. 自定義組件的基本結(jié)構(gòu)?
類繼承自 View盒犹,同時(shí)懂更,為該類定義了三個(gè)構(gòu)造方法并重寫了另外兩個(gè)方法:
構(gòu)造方法
public FirstView(Context context)
public FirstView(Context context, AttributeSet attrs)
public FirstView(Context context, AttributeSet attrs, int defStyleAttr)
這三個(gè)構(gòu)造方法的調(diào)用場(chǎng)景其實(shí)并不一樣,第一個(gè)只有一個(gè)參數(shù)急膀,在代碼中創(chuàng)建組件時(shí)會(huì)調(diào)用該構(gòu)造方法沮协,比如創(chuàng)建一個(gè)按鈕:Button btnOK = new Button(this),this 是指當(dāng)前的 Activity卓嫂,Activity 是 Context 的子類慷暂。第二個(gè)方法在 layout 布局文件中使用時(shí)調(diào)用,參數(shù) attrs 表示當(dāng)前配置中的屬性集合晨雳,例如在要 layout.xml 中定義一個(gè)按鈕:行瑞,Android 會(huì)調(diào)用第二個(gè)構(gòu)造方法 Inflate 出 Button 對(duì)象。而第三個(gè)構(gòu)造方法是不會(huì)自動(dòng)調(diào)用的餐禁,當(dāng)我們?cè)?Theme 中定義了 Style 屬性時(shí)通常在第二個(gè)構(gòu)造方法中手動(dòng)調(diào)用
?繪圖
protected void onDraw(Canvas canvas)
該方法我們?cè)偈煜げ贿^了血久,前面 5 個(gè)章節(jié)一直重寫了該方法,用于顯示組件的外觀帮非。最終的顯示結(jié)果需要通過 canvas 繪制出來氧吐。在 View 類中绷旗,該方法并沒有任何的默認(rèn)實(shí)現(xiàn)。
?測(cè)量尺寸
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
這是一個(gè) protected 方法副砍,意味著該方法主要用于子類的重寫和擴(kuò)展衔肢,如果不重寫該方法,父類 View 有自己的默認(rèn)實(shí)現(xiàn)豁翎。在 Android 中角骤,自定義組件的大小都由自身通過onMeasure()進(jìn)行測(cè)量,不管界面布局有多么復(fù)雜心剥,每個(gè)組件都負(fù)責(zé)計(jì)算自己的大小邦尊。
2.重寫 onMeasure 方法
View 類對(duì)于 onMeasure()方法有自己的默認(rèn)實(shí)現(xiàn)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(
getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在該方法中,調(diào)用了 protected final void setMeasuredDimension(int measured-Width, int
measuredHeight)方法應(yīng)用測(cè)量后的高度和寬度优烧,這是必須調(diào)用的蝉揍,以后我們可以調(diào)用
getMeasuredWidth()和 getMeasuredHeight()方法獲取這個(gè)寬度和高度值。
大部分情況下畦娄,protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法
都要重寫又沾,用于計(jì)算組件的寬度值和高度值。定義組件時(shí)熙卡,必須指定 android:layout_width 和
android:layout_height 屬性杖刷,屬性值有三種情況:match_parent、wrap_content 和具體值驳癌。
match_parent 表示組件的大小跟隨父容器滑燃,所在的容器有多大,組件就有多大颓鲜;wrap_content 表
示組件的大小則內(nèi)容決定表窘,比如 TextView 組件的大小由文字的多少?zèng)Q定,ImageView 組件的大小
由圖片的大小決定甜滨;如果是一個(gè)具體值乐严,相對(duì)就簡(jiǎn)單了,直接指定即可艳吠,單位為 dp麦备。
總結(jié)來說,不管是寬度還是高度昭娩,都包含了兩個(gè)信息:模式和大小。模式可能是match_parent黍匾、
wrap_content 和具體值的任意一種栏渺,大小則要根據(jù)不同的模式進(jìn)行計(jì)算。其實(shí) match_parent 也
是一個(gè)確定了的具體值锐涯,為什么這樣說呢磕诊?因?yàn)?match_parent 的大小跟隨父容器,而容器本身
也是一個(gè)組件,他會(huì)算出自己的大小霎终,所以我們根本不需要去重復(fù)計(jì)算了滞磺,父容器多大,組件就
有多大莱褒,View 的繪制流程會(huì)自動(dòng)將父容器計(jì)算好的大小通過參數(shù)傳過來击困。
模式使用三個(gè)不同的常量來區(qū)別:
? MeasureSpec.EXACTLY
當(dāng)組件的尺寸指定為 match_parent 或具體值時(shí)用該常量代表這種尺寸模式,很顯然广凸,
處于該模式的組件尺寸已經(jīng)是測(cè)量過的值阅茶,不需要進(jìn)行計(jì)算。
? MeasureSpec.AT_MOST
當(dāng)組件的尺寸指定為wrap_content時(shí)用該常量表示谅海,因?yàn)槌叽绱笮『蛢?nèi)容有關(guān)脸哀,所以,
我們要根據(jù)組件內(nèi)的內(nèi)容來測(cè)量組件的寬度和高度扭吁。比如 TextView 中的 text 屬性字符
串越長(zhǎng)撞蜂,寬度和高度就可能越大。
? MeasureSpec.UNSPECIFIED
未指定尺寸侥袜,這種情況不多谅摄,一般情況下,父控件為 AdapterView 時(shí)系馆,通過 measure 方
法傳入送漠。
最后,我們來考慮最關(guān)鍵的問題由蘑,如何獲得當(dāng)前組件的尺寸模式和尺寸大忻龉选?秘密隱藏在
protected void onMeasure(int widthMeasureSpec, int heightMeasure-Spec)方法的參數(shù)中尼酿,參數(shù)
widthMeasureSpec 和 heightMeasureSpec 看起來只是兩個(gè)整數(shù)爷狈,其實(shí)每個(gè)參數(shù)都包含了兩個(gè)值:
模式和尺寸。我們知道裳擎,int 類型占用 4 個(gè)字節(jié)涎永,一共 32 位,參數(shù) widthMeasureSpec 和
heightMeasureSpec 的前兩位代表模式鹿响,后 30 位則表示大小羡微。
真相大白,接下來繼續(xù)思考如何獲取 widthMeasureSpec 和 heightMeasureSpec 參數(shù)的前 2 位
與后 30 位惶我,其實(shí)通過位運(yùn)算即可得到妈倔,我們以 widthMeasureSpec 為例:
獲取尺寸模式:widthMeasureSpec & 0x3 << 30
獲取尺寸大小:widthMeasureSpec << 2 >> 2
上面的寫法不一而足绸贡,顯然盯蝴,這樣會(huì)給開發(fā)人員帶來難度毅哗,所以,提供了一個(gè)名為
MeasureSpec 的類用于計(jì)算模式和大信跬Α:
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
public class FirstView extends View {
public FirstView(Context context) {
super(context);
}
public FirstView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FirstView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureWidth(int widthMeasureSpec){
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
if(mode == MeasureSpec.EXACTLY){
//寬度為 match_parent 和具體值時(shí)虑绵,直接將 size 作為組件的寬度
width = size;
}else if(mode == MeasureSpec.AT_MOST){
//寬度為 wrap_content,寬度需要計(jì)算
}
return width;
}
private int measureHeight(int heightMeasureSpec){
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if(mode == MeasureSpec.EXACTLY){
//寬度為 match_parent 和具體值時(shí)闽烙,直接將 size 作為組件的高度
height = size;
}else if(mode == MeasureSpec.AT_MOST){
//高度為 wrap_content翅睛,高度需要計(jì)算
}
return height;
}
}
3.組件屬性
? ? 3.1.屬性的基本定義
除了 View 類中定義的默認(rèn)屬性外,我們也能自定義屬性鸣峭。自定義屬性主要有以下幾個(gè)步驟:
-> 在 res/values/attrs.xml 文件中為指定組件定義 declare-styleable 標(biāo)記宏所,并將所有的屬性都定義在該標(biāo)記中;
-> 在 layout 文件中使用自定義屬性摊溶;
-> 在組件類的構(gòu)造方法中讀取屬性值爬骤。在 res/values 目錄下,創(chuàng)建 attrs.xml 文件莫换,內(nèi)容大概如下:組件的屬性都應(yīng)該定義在 declare-styleable 標(biāo)記中霞玄,該標(biāo)記的 name 屬性值一般來說都是組件類的名稱(此處為 FirstView),雖然也可以取別的名稱拉岁,但和組件名相同可以提高代碼的可讀性坷剧。組件的屬性都定義在 declare-styleable 標(biāo)記內(nèi),成為 declare-styleable 標(biāo)記的子標(biāo)記喊暖,每個(gè)屬性由兩部分組成——屬性名和屬性類型惫企。屬性通過 attr 來標(biāo)識(shí),屬性名為 name陵叽,屬性類型為format狞尔,
屬性類型
-> string:字符串
-> boolean:布爾
-> color:顏色
-> dimension:尺寸,可以帶單位巩掺,比如長(zhǎng)度通常為 dp偏序,字體大小通常為 sp
-> enum:枚舉,需要在 attr 標(biāo)記中使用標(biāo)記定義枚舉值胖替,例如 sex 作為性別研儒,有兩個(gè)枚舉值:MALE 和 FEMALE。
?--> flag:標(biāo)識(shí)位独令,常見的 gravity 屬性就是屬性該類型
-> float:浮點(diǎn)數(shù)
-> fraction:百分?jǐn)?shù)端朵,在動(dòng)畫資源、等標(biāo)記中记焊,fromX逸月、fromY 等屬性就是
fraction 類型的屬性
-> integer:整數(shù)
-> reference : 引 用 , 引 用 另 一 個(gè) 資 源 遍膜, 比 如 android:paddingRight=-
"@dimen/activity_horizontal_margin"就是引用了一個(gè)尺寸資源
? ? 3.2.讀取來自 style 和 theme 中的屬性
組件的屬性可以在下面 4 個(gè)地方定義:
-> 組件
-> 組件的 style 屬性
-> theme
-> theme 的 style 屬性
這個(gè)問題說起來可能有點(diǎn)兒繞碗硬,所以我們索性通過一個(gè)案例來進(jìn)行學(xué)習(xí)和講解。假如我們有
一個(gè)組件類 AttrView瓢颅,從 View 類派生恩尾,AttrView 類有 4 個(gè)屬性:attr1、attr2挽懦、attr3翰意、attr4。另
外信柿,定義了一個(gè)屬性 myStyle冀偶,該屬性定義在 declare-styleable 標(biāo)記之外,類型為 reference渔嚷,用于
theme 的 style 屬性进鸠。這些屬性在 res/values/attrs.xml 文件中定義如下:
<?xml vesion="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="attrView">
<attr name="attr1" format="string"></attr>
</declare-styleable>
<attr name="myStyle" format="reference"></attr>
</resources>
例子1:
例子2:
好了暫時(shí)就寫到這里 ? 相關(guān)的知識(shí) 可以參考:
Android 自定義控件開發(fā)入門(一) - Android移動(dòng)開發(fā)技術(shù)文章_手機(jī)開發(fā) - 紅黑聯(lián)盟