ndroid中,你的應(yīng)用程序程序與View類組件有著一種固定的聯(lián)系,例如按鈕(Button)、 文本框(TextView), 可編輯文本框(EditText), 列表框(ListView), 復(fù)選框(CheckBox), 單選框(RadioButton), 滾動條(Gallery), 微調(diào)器(Spinner), 等等,還有一些比較先進的有著特殊用途的View組件,例如 AutoCompleteTextView, ImageSwitcher和 TextSwitcher。除此之外,種類繁多的像 線性布局(LinearLayout), 框架布局(FrameLayout), 這樣的布局組件(Layout)也被認為是View組件,他們是從View類派生過來的。
你的應(yīng)用程序就是這些控制組件和布局組件以某種方式結(jié)合顯示在屏幕上,一般來說這些組件對你來說基本夠用,但是你也應(yīng)該知道你是可以通過類繼承創(chuàng)建
屬于自己的組件,一般可以繼承像View、Layouts(布局組件)這樣的組件,甚至可以是一些比較高級的控制類組件。下面我們說一下為什么要繼承:
- 你可以為實現(xiàn)某種功能創(chuàng)建一個完全自定義風(fēng)格的組件,例如用二維的圖形創(chuàng)建控制組件實現(xiàn)聲音的控制,就像電子控制一樣。
- 你可以把幾種組件結(jié)合形成一個新的組件,你的組件可能同時包含ComboBox(一個能輸入的文本列表)和dual-pane selector control(左右兩個List窗口,你可以分配窗口每一項的從屬關(guān)系)等等。
- 你可以創(chuàng)建自己的布局組件(Layout)。SDK中的布局組件已經(jīng)提供了一系列的選項讓你打造屬于自己的應(yīng)用程序,但是高級的開發(fā)人員會發(fā)現(xiàn)根據(jù)現(xiàn)有的Layout組件開發(fā)新的Layout組件是很有必要的,甚至是完全從底層開發(fā)新的組件。
- 你可以覆蓋一個現(xiàn)有組件的顯示或功能。例如,改變EditText(可編輯文本)組件在屏幕上的顯示方式(可以參考Notepad的例子,里面教你如何創(chuàng)建一個下劃線的顯示頁面)。
- 你可以捕獲像按鍵按下這樣的事件,以一些通用的方法來處理這些事件(一個游戲的例子)。
為了實現(xiàn)某種目標你可能很有必要擴展一個已經(jīng)存在的View組件,下面我們結(jié)合一些例子教你如何去做。
內(nèi)容:
- 基本方法(The Basic Approach )
- 完全自定義組件(Fully Customized Components )
- 定制組件的例子(Customized Component Example )
- 組件的混合(或者控制類的混合) (Compound Components (or Compound Controls) )
- 修改現(xiàn)有組件(Tweaking an Existing Component )
- 小結(jié)(Go Forth and Componentize )
基本方法(The Basic Approach )
下面的一些步驟都比較概括,教你如何創(chuàng)建自己的組件:
- 讓你的類(Class)繼承一個現(xiàn)有的View 類或View的子類。
- 重載父類的一些方法:需要重載的父類方法一般以‘
on
’開頭,如onDraw(), onMeasure()和 onKeyDown()等等。
- 使用你的繼承類:一旦你的繼承類創(chuàng)建完成,你可以在基類能夠使用的地方使用你的繼承類,但完成功能就是你自己編寫的了。
繼承類能夠定義在activities里面,這樣你能夠方便的調(diào)用,但是這并不是必要的(或許在你的應(yīng)用程序中你希望創(chuàng)建一個所有人都可以使用的組件)。
完全自定義組件(Fully Customized Components)
完全自定義組件的方法可以創(chuàng)建一些用于顯示的圖形組件(graphical components),也許是一個像電壓表的圖形計量器,或者想卡拉OK里面顯示歌詞的小球隨著音樂滾動。無論那種方式,你也不能單純的利用組件的結(jié)合完成,無論你怎么結(jié)合這些現(xiàn)有的組件。
幸運的是,你可以以你自己的要求輕松地創(chuàng)建完全屬于自己的組件,你會發(fā)現(xiàn)不夠用的只是你的想象力、屏幕的尺寸和處理器的性能(記住你的應(yīng)用程序最后只會在那些性能低于桌面電腦的平臺上面運行)。
下面簡單介紹如何打造完全自定義的組件:
- 最為通用的VIEW類的父類毫無疑問是View類,因此,最開始你要創(chuàng)建一個基于此類的一個子類。
- 你可以寫一個構(gòu)造函數(shù)從XML文件中提取屬性和參數(shù),當(dāng)然你也可以自己定義這些屬性和參數(shù)(也許是圖形計量器的顏色和尺寸,或者是指針的寬度和幅度等等)
- 你可能有必要寫自己的事件監(jiān)聽器,屬性的訪問和修改函數(shù)和一些組件本身的功能上的代碼。
- 如果你希望組件能夠顯示什么東西,你很有可能會重載
onMeasure()
函數(shù),因而你就不得不重載 onDraw()
函數(shù)。當(dāng)兩個函數(shù)都用默認的,那么 onDraw()
函數(shù)將不會做任何事情,并且默認的 onMeasure()
函數(shù)自動的設(shè)置了一個100x100 —的尺寸,這個尺寸可能并不是你想要的。
- 其他有必要重載的
on...
系列函數(shù)都需要重新寫一次。
onDraw()
和onMeasure()
onDraw()
函數(shù)將會傳給你一個 Canvas 對象,通過它你可以在二維圖形上做任何事情,包括其他的一些標準和通用的組件、文本的格式,任何你可以想到的東西都可以通過它實現(xiàn)。
注意: 這里不包括三維圖形如果你想使用三維的圖形,你應(yīng)該把你的父類由View改為SurfaceView類,并且用一個單獨的線程??梢詤⒖糋LSurfaceViewActivity 的例子。
onMeasure()
函數(shù)有點棘手,因為這個函數(shù)是體現(xiàn)組件和容器交互的關(guān)鍵部分,onMeasure()應(yīng)該重載,讓它能夠有效而準確的表現(xiàn)它所包含部分的測量值。這就有點復(fù)雜了,因為我們不但要考慮父類的限制(通過onMeasure()傳過來的),同時我們應(yīng)該知道一旦測量寬度和高度出來后,就要立即調(diào)用setMeasuredDimension() 方法。
概括的來講,執(zhí)行onMeasure()
函數(shù)分為一下幾個階段:
- 重載的
onMeasure()
方法會被調(diào)用,高度和寬度參數(shù)同時也會涉及到(widthMeasureSpec
和heighMeasureSpec
兩個參數(shù)都是整數(shù)類型),同時你應(yīng)該考慮你產(chǎn)品的尺寸限制。這里詳細的內(nèi)容可以參考View.onMeasure(int, int) (這個連接內(nèi)容詳細的解釋了整個measurement操作)。
- 你的組件要通過
onMeasure()
計算得到必要的measurement長度和寬度從而來顯示你的組件,它應(yīng)該與規(guī)格保持一致,盡管它可以實現(xiàn)一些規(guī)格以外的功能(在這個例子里,父類能夠選擇做什么,包括剪切、滑動、提交異常或者用不同的參數(shù)又一次調(diào)用onMeasure()
函數(shù))。
- 一旦高度和寬度計算出來之后,必須調(diào)用
setMeasuredDimension(int width, int height)
,否則就會導(dǎo)致異常。
一個自定義組件的例子(A Customized Component Example)
在 API Demos 中的CustomView提供了以一個自定義組件的例子,這個自定義組件在 LabelView 類中定義。
LabelView例子涉及到了自定義組件的方方面面:
- 首先讓自定義組件從View類中派生出來。
- 編寫帶參數(shù)的構(gòu)造函數(shù)(參數(shù)可以來源于XML文件)。這里面的一些處理都已經(jīng)在View父類中完成,但是任然有些Labelview使用的自定義組件特有的新的參數(shù)需要處理。
- 一些標準的Public函數(shù),例如
setText()
, setTextSize()
, setTextColor()
- 重載
onMeasure()
方法來確定組件的尺寸(注意:在LabelView中是通過一個私有函數(shù)measureWidth()
來實現(xiàn)的)
- 重載
onDraw()
函數(shù)把Lable顯示在提供的canvas上。
在例子中,你可以通過custom_view_1.xml看到自定義組件LabelView的用法。在XML文件中特別要注意的是android:
和app:
兩個參數(shù)的混合運用,app:
參數(shù)表示應(yīng)用程序中被認為是LabelView組件的個體,這些也會作為資源在R類中定義。
組件混合技術(shù)Compound Components (or Compound Controls)
如果你不想創(chuàng)建一個完全自定義的組件,而是由幾個現(xiàn)有組件的組合產(chǎn)生的新的組件,那么混合組件技術(shù)就更加適合。簡單的來
說,這樣把幾個現(xiàn)有的組件融合到一個邏輯組合里面可以封裝成一個新的組件。例如,一個Combo
Box組件可以看作是是一個EditText和一個帶有彈出列表的Button組件的混合體。如果你點擊按鈕為列表選擇一項,
在Android中,其實還有其他的兩個View類可以做到類似的效果: Spinner和AutoCompleteTextView,,但是Combo Box作為一個例子更容易讓人理解。
下面簡單的介紹如何創(chuàng)建組合組件:
- 一般從Layout類開始,創(chuàng)建一個Layout類的派生類。也許在Combo
box我們會選擇水平方向的LinearLayout作為父類。記住,其他的Layout類是可以嵌套到里面的,因此混合組件可以是任何組件的混合。注
意,正如Activity一樣,你既可以使用外部XML文件來聲明你的組件,也可以嵌套在代碼中。
- 在新的混合組件的構(gòu)造函數(shù)中,首先,調(diào)用所有的父類的構(gòu)造函數(shù),傳入對應(yīng)的參數(shù)。然后可以設(shè)置你的混合組件的其他的一些方面,在哪創(chuàng)建
EditText組件,又在哪創(chuàng)建PopupList組件。注意:你同時也可以在XML文件中引入一些自己的屬性和參數(shù),這些屬性和參數(shù)也可以被你的混合
組件所使用。
- 你也可以創(chuàng)建時間監(jiān)聽器去監(jiān)聽新組件中View類觸發(fā)的事件,例如,對List選項單擊事件的監(jiān)聽,你必須在此時間發(fā)生后更新你EditText的值。
- 你可能創(chuàng)建自己的一些屬性,帶有訪問和修改方法。例如,允許設(shè)置EditText初始值并且提供訪問它的方法。
- 在Layout的派生類中,你沒有必要去重載
onDraw()
和onMeasure()
方法,因為Layout會有比較好的默認處理。但是,如果你覺得有必要你也可以重載它。
- 你也可能重載一些
on
系列函數(shù),例如通過onKeyDown()
的重載,你可以通過按某個鍵去選擇列表中的對應(yīng)的值。
總之,把Layout類作為基類有下面幾個優(yōu)點:
- 正如activity一樣,你也可以通過XML文件去聲明你的新組件,或者你也可以在代碼中嵌套。
onDraw()
函數(shù)和onMeasure()
函數(shù)是沒有必要重載的,兩個函數(shù)已經(jīng)做得很好了。
- 你可以很快的創(chuàng)建你的混合組件,并且可以像單一組件那樣使用。
混合組件的例子(Examples of Compound Controls)
In the API Demos project 在API Demos工程中,有兩個List類的例子——Example 4和Example 6,里面的SpeechView組件是從LinearLayout類派生過來,實現(xiàn)顯示演講顯示功能,對應(yīng)的原代碼是List4.java
和List6.java
。
調(diào)整現(xiàn)有組件(Tweaking an Existing Component)
在某些情況下,你可能有更簡單的方法去創(chuàng)建你的組件。如果你應(yīng)經(jīng)有了一個非常類似的組件,你所要做的只是簡單的從這個組件派生出你的組件,重在其中
一些有必要修改的方法。通過完全自定義組件的方法你也可以同樣的實現(xiàn),但通過沖View派生產(chǎn)生新的組件,你可以簡單獲取一些已經(jīng)存在的處理機制,這些很
可能是你所想要的,而沒有必要從頭開始。
例如,在SDK中有一個NotePad的例子(NotePad application )。該例子演示了很多Android平臺實用的細節(jié),例如你會學(xué)到從EditView派生出能夠自動換行的記事本。這還不是一個完美的例子,因為相比早期的版本來說,這些API已經(jīng)感變了很多,但它確實說明了一些問題。
如果你還未查看該程序,現(xiàn)在你就可以在Eclipse中導(dǎo)入記事本例程(或僅通過提供的鏈接查看相應(yīng)的源代碼)。特別是查看NoteEditor.java 中的MyEditText
的定義。
下面有幾點要注意的地方:
- 聲明(The Definition)
這個類是通過下面一行代碼來定義的:
public static class MyEditText extends EditText
- 它是定義在
NoteEditor activity
類里面的,但是它是共有的(public),因此如果有必要,它可以通過NoteEditor.MyEditText
從NoteEditor
外面來調(diào)用。
- 它是
static
類(靜態(tài)類),意味著不會出現(xiàn)所謂的通過父類訪問數(shù)據(jù)的“虛態(tài)方法”, 這樣就使該類成為一個可以不嚴重依賴NoteEditor
的單獨類。對于不需要從外部類訪問的內(nèi)聯(lián)類的創(chuàng)建,這是一個很清晰地思路,保證所產(chǎn)生的類很小,并且允許它可以被其他的類方便的調(diào)用。
- 它是
EditText
類的擴展,它是我們選擇的用來自定義的父類。當(dāng)我們完成以后,新的類就可以作為一個普通的EditText
來使用。
- 類的初始化
一般來說,父類是首先調(diào)用的。進一步來說,這不是一個默認的構(gòu)造函數(shù),而是一個帶參數(shù)的構(gòu)造函數(shù)。因為EditText
是使用從XML布局文件提取出來的參數(shù)進行創(chuàng)建,因此我們的構(gòu)造函數(shù)也要取出參數(shù)并且將這些參數(shù)傳遞給父類。
- 方法重載
在本例中,僅對onDraw()
一個方法進行重載。但你可以很容易地為你的定制組件重載其他需要的方法。
對于記事本例子來說,通過重載onDraw()
方法我們可以在EidtView
的畫布(canvas
)上繪制藍色的線條(canvas
類是通過重寫的onDraw()
方法傳遞)。該函數(shù)快要結(jié)束時要調(diào)用super.onDraw()
函數(shù)。父類的方法是應(yīng)該調(diào)用,但是在這個例子里面,我們是在我們劃好了藍線之后調(diào)用的。
- 使用定制組件
現(xiàn)在,我們已經(jīng)有自己定制的組件了,但是應(yīng)該怎樣使用它呢?在記事本例子中,定制的組件直接在預(yù)定義的布局文件中使用,讓我們看一看res/layout
目錄中的note_editor.xml文件。
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.notepad.NoteEditor$MyEditText"
id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:drawable/empty"
android:padding="10dip"
android:scrollbars="vertical"
android:fadingEdge="vertical" />
- 該自定義組件在XML中是作為一個一般的View類來創(chuàng)建的,并且是通過全路徑包來描述的。注意這里內(nèi)聯(lián)類是通過
NoteEditor$MyEditText
來表示的,這是Java編程中引用內(nèi)聯(lián)類的標準方法。
- 在定義中的其他屬性和參數(shù)將傳遞給定制組件的構(gòu)造函數(shù),然后才傳到EditText構(gòu)造函數(shù)中,因此這些參數(shù)也是你使用EditText組件的參數(shù)。注意,這里你也可以增加你自己的參數(shù),我們將在下面討論這個問題。
這就是你全部需要做的,誠然這是一個簡單的例子。但問題的關(guān)鍵是:你的需求有多復(fù)雜,那么你的自定義組件就有多么復(fù)雜。
一個更為復(fù)雜的組件可能需要重載更多的on
系列函數(shù),并且還要很多特有的函數(shù)來充分實現(xiàn)自定義組件的功能。唯一的限制就是你的想象力和你需要組件去執(zhí)行什么工作。
現(xiàn)在開始你的組件化之旅吧
如你所見,Android提供了一種精巧而又強大的組件模型,讓你盡可能的完成你的工作。從簡單的組件調(diào)整到組件混合,甚至完全自定義組件,靈活的運用這些技術(shù),你應(yīng)該可以得到一個完全符合你外觀要求的的Android程序