淺談我對(duì)Android自定義控件的認(rèn)識(shí)

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)盟

Android 自定義View (一) - Hongyang - 博客頻道 - CSDN.NET

一起來學(xué)習(xí)Android自定義控件1 - OPEN 開發(fā)經(jīng)驗(yàn)庫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市形病,隨后出現(xiàn)的幾起案子客年,更是在濱河造成了極大的恐慌,老刑警劉巖漠吻,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件量瓜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡途乃,警方通過查閱死者的電腦和手機(jī)绍傲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耍共,“玉大人烫饼,你說我怎么就攤上這事』幔” “怎么了枫弟?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鹏往。 經(jīng)常有香客問我淡诗,道長(zhǎng),這世上最難降的妖魔是什么伊履? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任韩容,我火速辦了婚禮,結(jié)果婚禮上唐瀑,老公的妹妹穿的比我還像新娘群凶。我一直安慰自己,他們只是感情好哄辣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布请梢。 她就那樣靜靜地躺著赠尾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毅弧。 梳的紋絲不亂的頭發(fā)上气嫁,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音够坐,去河邊找鬼寸宵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛元咙,可吹牛的內(nèi)容都是我干的梯影。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庶香,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼甲棍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起脉课,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤救军,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后倘零,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唱遭,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年呈驶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拷泽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡袖瞻,死狀恐怖司致,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聋迎,我是刑警寧澤脂矫,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站霉晕,受9級(jí)特大地震影響庭再,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牺堰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一拄轻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伟葫,春花似錦恨搓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽常拓。三九已至,卻和暖如春夺姑,著一層夾襖步出監(jiān)牢的瞬間墩邀,已是汗流浹背掌猛。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工盏浙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荔茬。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓废膘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親慕蔚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丐黄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容