1、創建自定義組件Building Custom Components
Android提供了一個精緻而強大的組件化模式來創建你的用戶界面,基於基礎的佈局類:視圖View和視圖組ViewGroup。平臺包含了多種預定義視圖和視圖組子類-分別稱爲部件和佈局-這些可以用來構造你的用戶界面。
一部分可用部件包括按鈕Button,文本視圖TextView,編輯文本框EditText,列表視圖ListView,組合框CheckBox,單選按鈕RadioButton, 畫廊Gallery, 微調器Spinner,以及一些用於特定場合的自動補全文本視圖AutoCompleteTextView, 圖片切換器ImageSwitcher,和文本切換器TextSwitcher.
可用佈局有線性佈局LinearLayout, 框架佈局FrameLayout, 相對佈局RelativeLayout,以及其他。更多例子,參見常用佈局對象Common Layout Objects.
如果這些預定義的部件或佈局不能滿足你的需求,你可以創建你自己的視圖類。如果你只需要在現有的部件或佈局上做些調整,你只需要子類化這個部件或佈局並重寫它的方法。
創建自己的視圖子類讓你可以精確控制界面元素的外形和功能。爲了對這種控制有個大概的印象,下面是一些例子說明你可以用它們做什麼:
·你可以創建一個完全自定義繪製的視圖類型,比如一個用2D圖形繪製的“音量控制”旋鈕,使它看上去像一個模擬電子件。
·你可以把一組視圖組件組合進一個單獨的組件裏,也許像一個組合框(是彈出列表和自由輸入的文本段的組合),一個雙窗格選擇器控件(一個左窗格和右窗格,裏面各有一個列表,你可以選擇哪個項應該在哪個列表中),如此等等。
·你可以重寫一個編輯文本EditText組件的繪製方式(記事本指南NotepadTutorial中使用這個方法創建了一個條紋狀的記事本頁面效果)。
·你可以捕獲其他事件如按鍵然後以自定義的方式處理。(比如一個遊戲所做的那樣)
下面的章節解釋了怎麼去創建自定義視圖和在你的應用程序中使用它們。詳細的參考信息,請查看視圖View 類。
2、基本方法The Basic Approach
下面是關於怎麼樣開始創建自定義視圖組件的一個概要性的總體描述:
1. 擴展一個現有的視圖類然後子類化它。
2. 重寫父類中的一些方法。這些方法以“on”開始,比如onDraw(), onMeasure(),和onKeyDown()。這和活動或列表活動中爲生命週期和其他功能鉤子重寫on…事件類似。
3. 使用你的新擴展類。這些完成後,你的擴展類就可以替代那個基礎視圖了。
提示: 擴展類可以被定義成使用它們的活動的內部類。這樣活動可以控制對它們的訪問,這很有用但並非必須如此(也許你想在應用程序裏創建一個使用更廣的公共視圖)。
3、完全自定義組件Fully Customized Components
完全自定義組件可以用來創建你想要的圖形組件。也許是一個圖形VU表看起來像一個老式的模擬計量器,或者是一個伴唱字幕,上面有一個彈球隨着文字移動這樣你就可以在卡拉OK機上跟唱。無論哪種方式,你想要的東西都是內置組件所不能完成的,不管你怎麼組合它們。
幸運的是,你可以簡單的按照你的意願來創建組件,除非你想不到,或者受限於屏幕尺寸,以及可用電源(記住,最終你的應用程序得運行在比桌面工作站電源少得多的設備上)。
要創建一個完全自定義組件:
1. 毫無意外,你可以擴展的最通用的視圖是View, 因此你通常從擴展它開始創建你的超級組件。
2. 你可以供應一個構造器從XML中讀取屬性和參數,你也可以消費你自己的屬性和參數(也許是VU表的顏色和範圍,或者指針的寬度和阻尼,等)
3. 你可能也想在你的組件中創建自己的事件偵聽器,屬性訪問和修改器,以及其它可能更復雜的行爲。
4. 你將幾乎肯定要重寫onMeasure()而且也很可能需要重寫onDraw()。如果你希望這個組件顯示一些東西。兩者都有缺省行爲,缺省的onDraw()方法不做任何事情,而缺省onMeasure()方法將總是設置一個100x100的尺寸-這或許不是你想要的。
5. 其他on...方法也可以按照要求重寫。
4、擴展onDraw()和onMeasure()Extend onDraw()and onMeasure()
onDraw()方法傳給你一個畫布Canvas對象,你可以在上面實現任何你想要的東西:2D圖形,其它基礎或自定義組件,風格文本,或其他任何你能想到的。
注意: 這不適用於3D圖形。如果你想使用3D圖形,你必須擴展SurfaceView而不是View,並且從一個單獨的線程中繪製。參見GLSurfaceViewActivity示例以瞭解更多細節。.
onMeasure()會被用得更多一點。onMeasure()是你的組件和它的容器之間繪製約定的關鍵部分。onMeasure()應該被重寫來有效和準確的報告它所包含部分的尺寸。但是由於父類的一些限制性要求(被傳遞給onMeasure()方法)和以寬度和高度(一旦已經被計算出來)調用setMeasuredDimension()方法的要求,這將變得稍微複雜一些。如果你從一個重寫的onMeasure()方法中調用這個方法失敗,結果將返回一個測量時異常。
概要而言,實現onMeasure()看起來如下:
1.給定寬度 和高度測量規格(widthMeasureSpec和heightMeasureSpec,兩者都是代表維度的整數編碼)來調用重寫的onMeasure方法,這應該被當作度量上的限制性要求。完整的參考在View.onMeasure(int, int)文檔中,這篇參考文檔還很好的描述了整個測量操作)。
2.你的組件的onMeasure()方法應該計算一個寬度和高度用來繪製這個組件。它應該儘可能留在傳遞的規格所指定的範圍裏,儘管它可以選擇超出它們(這樣的話,父類可以選擇處理方式,包括裁剪,滾動,拋出異常,或者要求onMeasure()再試一次,也許會使用不同的度量規格。)
3.一旦寬度和高度被計算出來,這個setMeasuredDimension(int width, intheight)方法必須以計算出來的度量來調用。如果調用不成功,則將拋出異常。
下面是框架在視圖上調用的一些其他基本方法的彙總:
類別Category | 方法Methods | 描述Description |
創建 | 構造器 | 有一種構造器形式是在從代碼裏創建視圖時被調用,另一種是從一個佈局文件中擴充視圖時被調用。第二種形式應該解析並運用任何定義在佈局文件中的屬性。 |
創建 | onFinishInflate() | 當一個視圖及其所有子項已經在XML中擴充好時被調用。 |
佈局 | onMeasure(int, int) | 用來決定這個視圖及其所有子項的尺寸要求。 |
佈局 | onLayout(boolean, int, int, int, int) | 當這個視圖應該爲它所有的子項分配一個尺寸和位置的時候調用。 |
佈局 | onSizeChanged(int, int, int, int) | 當這個視圖的尺寸被改變時被調用。 |
繪畫 | onDraw(Canvas) | 當視圖需要繪製其內容時被調用。 |
事件處理 | onKeyDown(int, KeyEvent) | 當一個按鍵事件發生時被調用。 |
事件處理 | onKeyUp(int, KeyEvent) | 當一個按鍵釋放事件發生時調用。 |
事件處理 | onTrackballEvent(MotionEvent) | 當一個跟蹤球動作事件發生時被調用。 |
事件處理 | onTouchEvent(MotionEvent) | 當一個觸摸屏動作事件發生時被調用。 |
Focus | onFocusChanged(boolean, int, Rect) | 當視圖獲取或丟失焦點時被調用。 |
Focus | onWindowFocusChanged(boolean) | 當包含視圖的窗口獲取或丟失焦點時被調用。 |
Attaching | onAttachedToWindow() | 當視圖被附着到一個窗口時被調用。 |
Attaching | onDetachedFromWindow() | 當視圖從一個窗口拆分開時被調用。 |
Attaching | onWindowVisibilityChanged(int) | 當包含視圖的窗口的可見性發生改變時被調用。 |
5、一個自定義視圖示例A Custom View Example
這個在API Demos中的CustomView例子提供了自定義視圖的示範。這個自定義視圖定義在 LabelView類中。
LabelView例子說明了自定義組件的很多不同方面:
·爲一個完全自定義組件擴展視圖類。
·參數化的構造器,採用視圖擴充參數(這些參數定義在XML中)。其中一些被傳遞給視圖超類,但更重要的是,有一些自定義屬性被LabelView所用。
·基本的公共方法來設置一個label組件你所期望的類型,比如setText(), setTextSize(),setTextColor() 等等。
·一個重寫的onMeasure方法來決定和設置這個組件的繪製尺寸。(注意在LabelView中,實際工作是通過一個私有的measureWidth()方法來完成的。)
·一個重寫的onDraw()方法來把標籤(label)畫到提供的畫布canvas中。 method to draw thelabel onto the provided canvas.
你可以在custom_view_1.xml中查閱一些使用範例。特別是,你可以看到android:命名空間參數和自定義app:命名空間參數的一個混合。這些app:參數是LabelView認識和使用的,並被定義在R資源類的一個可格式化的內部類裏。
6、複合控件Compound Controls
如果你不想創建一個完全自定義的組件,但希望由一組已有控件來組裝成一個可複用組件,那麼創建一個複合組件(或複合控件)可以滿足要求。在一個小容器裏,把一些更原子的控件(或視圖)整合進一個邏輯項目組中,從而可以被當作單個控件來對待。比如,一個組合框,可以被看作是一個單行文本編輯控制和一個附有彈出列表的相鄰按鈕的組合。如果你按下這個按鈕並從列表中選擇一些項,它將被生成到文本編輯域中,不過用戶也可以直接在文本編輯域進行輸入如果願意的話。
在Android裏,事實上有另外兩種視圖也是這樣做的:Spinner和AutoCompleteTextView,不過,組合框的概念比較容易理解。
爲了創建一個複合組件:
1. 通常的起點是某種類型的佈局,因此創建一個擴展布局的類。可能在組合框例子中,我們會使用一個水平方向的線性佈局LinearLayout。記住其它佈局可以被嵌套在裏面,這樣這個複合組件可以任意複雜和結構化。注意就像一個視圖,你既可以使用聲明(基於XML)方式來創建這個包含的組件,也可以通過編碼在程序中嵌入它們。
2.在這個新類的構造函數中,採用任何超類期望的參數,並首先傳遞給超類構造函數。然後你可以建立用於新組件的其它視圖;這就是你將創建EditText域和PopupList的地方。注意你還可能需要引入你自己的屬性和參數到XML中,這些將被提取出來併爲你的構造函數所用。
3.你還可以爲包含的視圖可能產生的事件創建偵聽器,比如,一個列表項點擊偵聽器的方法,用來在一個列表項被選中時更新相應的文本編輯框內容。
4. 你可能還想通過訪問和修改函數來創建自己的特性,比如,允許EditText值可以被初始化並在需要時被查詢到。
5. 在擴展一個佈局時,你不需要重寫onDraw()和onMeasure()方法,因爲佈局會有缺省行爲。不過,如果需要的話,你同樣可以重寫它們。
6. 你可能會重寫其它on...方法,比如onKeyDown(),當一個特定鍵被按下時可能會從彈出列表中選擇某個特定的默認值。
總之,基於佈局創建自定義控件有如下一些好處,包括:
·你可以像活動屏幕一樣使用聲明性的XML來指定佈局,或者通過編程來實現視圖嵌套。
·onDraw()和onMeasure()方法(加上大多數其它on...方法)將可能有合適的行爲,這樣你就不需要重寫它們。
·最後,你可以很快地構建任意複雜的複合視圖以及把它們當作單個組件來重用。
複合控件的例子Examples of Compound Controls
在隨SDK發佈的APIDemos項目中,有兩個列表例子-例4和例6,在Views/Lists下面,演示了一個從線性佈局擴展的SpeechView,該組件用來顯示演講摘錄。相應的類在List4.Java 和List6.java裏。
7、修改一個已有視圖類型Modifying an Existing View Type
在某些情況下有更簡單實用的方法來創建自定義視圖。如果有一個視圖已經和你想要的很相似,你只需要在這個組件上直接擴展並重寫相關行爲方法即可。通過完全自定義的組件你當然可以做任何事,不過通過一個視圖層次圖中的特定類,你也一樣可以做很多事來滿足需求。
比 如,SDK在這些例子中包含了一個記事本應用程序NotePad application。該程序演示了Android平臺應用的很多方面,其中之一就是擴展一個EditText視圖來創建一個條紋記事本。這並非一個完美的例子,所使用的APIs可能有所變化,但它的確說明了原理。
如果你沒有這麼做過,導入記事本例子到Eclipse中。特別是看一下NoteEditor.java文件中的MyEditText定義。
下面是一些值得注意的地方:
1. 定義TheDefinition
這個類以下面的代碼行定義:
public static class MyEditText extends EditText
o 它被定義爲NoteEditor活動的內部類,但它是公共的,因此它可以在NoteEditor類之外以NoteEditor.MyEditText來訪問。
o 它是靜態的,這意味着它不產生允許它從父類中訪問數據的所謂的“僞方法”,這就意味着它的行爲更像一個獨立的類而不是和NoteEditor有着很強的關聯。這是一個用來創建內部類的簡潔方式,如果這些類不需要從外部類中訪問狀態,這使得產生的類小巧且很容易從被其它類所使用。
o 它擴展了EditText,我們選擇基於這個視圖來自定義組件。這些結束後,新的類將能夠被用來代替通常的EditText視圖。
2. 類初始化Class Initialization
通常,先調用超類。此外,這並非缺省構造器,而是一個參數化的構造器。這個EditText通過擴充在XML佈局文件中的這些參數創建,這樣,我們的構造器同樣需要採用它們並傳遞給超類構造器。
3. 重寫方法Overridden Methods
這個例子中,只有一個方法需要被重寫:onDraw()- 但是當創建你自己的組件時,可能需要重寫其它方法。
對於記事本NotePad例子,重寫onDraw()方法可允許我們在EditText視圖畫布上繪製藍線(這個畫布canvas被傳給這個重寫的onDraw()方法)。這個super.onDraw()方法在這個方法結束前被調用。這個超類方法應該被調用,但在這個例子裏,我們是在畫好線後才調用它。
4. 使用自定義組件Use the CustomComponent
現在我們已經有了自定義組件,但我們該怎麼用它呢?在記事本NotePad例子中,這個自定義組件直接在佈局聲明中使用,所以看一下res/layout目錄下的note_editor.xml文件。
<view
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中以一個通用視圖創建,而這個類用包全名來指定。還需要注意到這個我們定義的內部類通過NoteEditor$MyEditText標記來引用,這在Java編程語言中是一個引用內部類的標準方式。
如果你的自定義視圖組件不是定義爲一個內部類,那麼你可以,有選擇的,以XML元素名來聲明這個視圖組件,並且不需要包含class屬性。比如:
<com.android.notepad.MyEditText
id="@+id/note"
... />
注意MyEditText 類現在是一個獨立的類文件,當這個類被嵌入到NoteEditor類裏時,這個技術無效。
定義中的其它屬性和參數被傳遞給自定義組件的構造函數,然後傳遞給EditText的構造函數,因此這些是和你用於EditText視圖的相同的參數。注意你同樣可以添加自己的參數,下面會再談到這一點。
這就是所有的內容。我承認這只是一個簡單的例子,但關鍵是-創建自定義組件的複雜度只和你的需求有關。
一個更復雜的組件可以重寫更多的on...方法並引入一些它自己的幫助(helper)方法,大量的定製它的特性和行爲。唯一的限制在於你的想象力和你想讓這個組件做什麼。