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

Android已經(jīng)為我們提供了大量的View供我們使用,但是可能有時候這些組件不能滿足我們的開發(fā)中的需求,這時候就需要自定義控件了。自定義控件對于初學(xué)者總是感覺是一種復(fù)雜的技術(shù)。因為里面涉及到的知識點會比較多。但是任何復(fù)雜的技術(shù)后面都是一點點簡單知識的積累。通過對自定義控件的學(xué)習(xí)去可以更深入的掌握android的相關(guān)知識點,所以學(xué)習(xí)android自定義控件是很有必要的。記得以前學(xué)習(xí)總是想著去先理解很多知識點,然后再來學(xué)著自定義控件,但是每次寫自定義控件的時候總是不知道從哪里下手啊。后來在學(xué)習(xí)的過程中發(fā)現(xiàn)自己跟著去寫一些簡單的自定義控件,然后在這個過程中遇到了沒有掌握的知識點再去學(xué)習(xí)。不僅自定義控件的能力有所提高。其它的知識也有了很好的鞏固和認(rèn)識。

1. 自定義組件的基本結(jié)構(gòu)?

類繼承自 View,同時,為該類定義了三個構(gòu)造方法并重寫了另外兩個方法:

構(gòu)造方法

public FirstView(Context context)

public FirstView(Context context, AttributeSet attrs)

public FirstView(Context context, AttributeSet attrs, int defStyleAttr)

這三個構(gòu)造方法的調(diào)用場景其實并不一樣,第一個只有一個參數(shù),在代碼中創(chuàng)建組件時會調(diào)用該構(gòu)造方法,比如創(chuàng)建一個按鈕:Button btnOK = new Button(this),this 是指當(dāng)前的 Activity,Activity 是 Context 的子類。第二個方法在 layout 布局文件中使用時調(diào)用,參數(shù) attrs 表示當(dāng)前配置中的屬性集合,例如在要 layout.xml 中定義一個按鈕:,Android 會調(diào)用第二個構(gòu)造方法 Inflate 出 Button 對象。而第三個構(gòu)造方法是不會自動調(diào)用的,當(dāng)我們在 Theme 中定義了 Style 屬性時通常在第二個構(gòu)造方法中手動調(diào)用

?繪圖

protected void onDraw(Canvas canvas)

該方法我們再熟悉不過了,前面 5 個章節(jié)一直重寫了該方法,用于顯示組件的外觀。最終的顯示結(jié)果需要通過 canvas 繪制出來。在 View 類中,該方法并沒有任何的默認(rèn)實現(xiàn)。

?測量尺寸

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

這是一個 protected 方法,意味著該方法主要用于子類的重寫和擴(kuò)展,如果不重寫該方法,父類 View 有自己的默認(rèn)實現(xiàn)。在 Android 中,自定義組件的大小都由自身通過onMeasure()進(jìn)行測量,不管界面布局有多么復(fù)雜,每個組件都負(fù)責(zé)計算自己的大小。

2.重寫 onMeasure 方法

View 類對于 onMeasure()方法有自己的默認(rèn)實現(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)用測量后的高度和寬度,這是必須調(diào)用的,以后我們可以調(diào)用

getMeasuredWidth()和 getMeasuredHeight()方法獲取這個寬度和高度值。

大部分情況下,protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法

都要重寫,用于計算組件的寬度值和高度值。定義組件時,必須指定 android:layout_width 和

android:layout_height 屬性,屬性值有三種情況:match_parent、wrap_content 和具體值。

match_parent 表示組件的大小跟隨父容器,所在的容器有多大,組件就有多大;wrap_content 表

示組件的大小則內(nèi)容決定,比如 TextView 組件的大小由文字的多少決定,ImageView 組件的大小

由圖片的大小決定;如果是一個具體值,相對就簡單了,直接指定即可,單位為 dp。

總結(jié)來說,不管是寬度還是高度,都包含了兩個信息:模式和大小。模式可能是match_parent、

wrap_content 和具體值的任意一種,大小則要根據(jù)不同的模式進(jìn)行計算。其實 match_parent 也

是一個確定了的具體值,為什么這樣說呢?因為 match_parent 的大小跟隨父容器,而容器本身

也是一個組件,他會算出自己的大小,所以我們根本不需要去重復(fù)計算了,父容器多大,組件就

有多大,View 的繪制流程會自動將父容器計算好的大小通過參數(shù)傳過來。

模式使用三個不同的常量來區(qū)別:

? MeasureSpec.EXACTLY

當(dāng)組件的尺寸指定為 match_parent 或具體值時用該常量代表這種尺寸模式,很顯然,

處于該模式的組件尺寸已經(jīng)是測量過的值,不需要進(jìn)行計算。

? MeasureSpec.AT_MOST

當(dāng)組件的尺寸指定為wrap_content時用該常量表示,因為尺寸大小和內(nèi)容有關(guān),所以,

我們要根據(jù)組件內(nèi)的內(nèi)容來測量組件的寬度和高度。比如 TextView 中的 text 屬性字符

串越長,寬度和高度就可能越大。

? MeasureSpec.UNSPECIFIED

未指定尺寸,這種情況不多,一般情況下,父控件為 AdapterView 時,通過 measure 方

法傳入。

最后,我們來考慮最關(guān)鍵的問題,如何獲得當(dāng)前組件的尺寸模式和尺寸大???秘密隱藏在

protected void onMeasure(int widthMeasureSpec, int heightMeasure-Spec)方法的參數(shù)中,參數(shù)

widthMeasureSpec 和 heightMeasureSpec 看起來只是兩個整數(shù),其實每個參數(shù)都包含了兩個值:

模式和尺寸。我們知道,int 類型占用 4 個字節(jié),一共 32 位,參數(shù) widthMeasureSpec 和

heightMeasureSpec 的前兩位代表模式,后 30 位則表示大小。

真相大白,接下來繼續(xù)思考如何獲取 widthMeasureSpec 和 heightMeasureSpec 參數(shù)的前 2 位

與后 30 位,其實通過位運算即可得到,我們以 widthMeasureSpec 為例:

獲取尺寸模式:widthMeasureSpec & 0x3 << 30

獲取尺寸大?。簑idthMeasureSpec << 2 >> 2

上面的寫法不一而足,顯然,這樣會給開發(fā)人員帶來難度,所以,提供了一個名為

MeasureSpec 的類用于計算模式和大?。?/p>

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 和具體值時,直接將 size 作為組件的寬度

width = size;

}else if(mode == MeasureSpec.AT_MOST){

//寬度為 wrap_content,寬度需要計算

}

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 和具體值時,直接將 size 作為組件的高度

height = size;

}else if(mode == MeasureSpec.AT_MOST){

//高度為 wrap_content,高度需要計算

}

return height;

}

}

3.組件屬性

? ? 3.1.屬性的基本定義

除了 View 類中定義的默認(rèn)屬性外,我們也能自定義屬性。自定義屬性主要有以下幾個步驟:

-> 在 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)記,每個屬性由兩部分組成——屬性名和屬性類型。屬性通過 attr 來標(biāo)識,屬性名為 name,屬性類型為format,

屬性類型

-> string:字符串

-> boolean:布爾

-> color:顏色

-> dimension:尺寸,可以帶單位,比如長度通常為 dp,字體大小通常為 sp

-> enum:枚舉,需要在 attr 標(biāo)記中使用標(biāo)記定義枚舉值,例如 sex 作為性別,有兩個枚舉值:MALE 和 FEMALE。

?--> flag:標(biāo)識位,常見的 gravity 屬性就是屬性該類型

-> float:浮點數(shù)

-> fraction:百分?jǐn)?shù),在動畫資源、等標(biāo)記中,fromX、fromY 等屬性就是

fraction 類型的屬性

-> integer:整數(shù)

-> reference : 引 用 , 引 用 另 一 個 資 源 , 比 如 android:paddingRight=-

"@dimen/activity_horizontal_margin"就是引用了一個尺寸資源

? ? 3.2.讀取來自 style 和 theme 中的屬性


組件的屬性可以在下面 4 個地方定義:

-> 組件

-> 組件的 style 屬性

-> theme

-> theme 的 style 屬性

這個問題說起來可能有點兒繞,所以我們索性通過一個案例來進(jìn)行學(xué)習(xí)和講解。假如我們有

一個組件類 AttrView,從 View 類派生,AttrView 類有 4 個屬性:attr1、attr2、attr3、attr4。另

外,定義了一個屬性 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:


好了暫時就寫到這里 ? 相關(guān)的知識 可以參考:

Android 自定義控件開發(fā)入門(一) - Android移動開發(fā)技術(shù)文章_手機(jī)開發(fā) - 紅黑聯(lián)盟

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容