構建自定義元件

yangxi_001發表於2013-12-04

Android中,你的應用程式程式與View類元件有著一種固定的聯絡,例如按鈕(Button)、 文字框(TextView),可編輯文字框(EditText)列表框(ListView)核取方塊(CheckBox)單選框(RadioButton)滾動條(Gallery)微調器(Spinner), 等等,還有一些比較先進的有著特殊用途的View元件,例如AutoCompleteTextViewImageSwitcher和 TextSwitcher。除此之外,種類繁多的像 線性佈局(LinearLayout),框架佈局(FrameLayout), 這樣的佈局元件(Layout)也被認為是View元件,他們是從View類派生過來的。

你的應用程式就是這些控制元件和佈局元件以某種方式結合顯示在螢幕上,一般來說這些元件對你來說基本夠用,但是你也應該知道你是可以通過類繼承建立屬於自己的元件,一般可以繼承像View、Layouts(佈局元件)這樣的元件,甚至可以是一些比較高階的控制類元件。下面我們說一下為什麼要繼承:

  • 你可以為實現某種功能建立一個完全自定義風格的元件,例如用二維的圖形建立控制元件實現聲音的控制,就像電子控制一樣。
  • 你可以把幾種元件結合形成一個新的元件,你的元件可能同時包含ComboBox(一個能輸入的文字列表)和dual-pane selector control(左右兩個List視窗,你可以分配視窗每一項的從屬關係)等等。
  • 你可以建立自己的佈局元件(Layout)。SDK中的佈局元件已經提供了一系列的選項讓你打造屬於自己的應用程式,但是高階的開發人員會發現根據現有的Layout元件開發新的Layout元件是很有必要的,甚至是完全從底層開發新的元件。
  • 你可以覆蓋一個現有元件的顯示或功能。例如,改變EditText(可編輯文字)元件在螢幕上的顯示方式(可以參考Notepad的例子,裡面教你如何建立一個下劃線的顯示頁面)。
  • 你可以捕獲像按鍵按下這樣的事件,以一些通用的方法來處理這些事件(一個遊戲的例子)。

為了實現某種目標你可能很有必要擴充套件一個已經存在的View元件,下面我們結合一些例子教你如何去做。

內容:

基本方法(The Basic Approach )
完全自定義元件(Fully Customized Components )
定製元件的例子(Customized Component Example )
元件的混合(或者控制類的混合) (Compound Components (or Compound Controls) )
修改現有元件(Tweaking an Existing Component )
小結(Go Forth and Componentize )

基本方法(The Basic Approach )

下面的一些步驟都比較概括,教你如何建立自己的元件:

  1. 讓你的類(Class)繼承一個現有的View 類或View的子類。
  2. 過載父類的一些方法:需要過載的父類方法一般以‘on’開頭,如onDraw()onMeasure()和 onKeyDown()等等。
    • 這個在Activity 或則 ListActivity 派生中同樣適用,你需要過載一些生命週期函式和一些其他功能性的HOOK函式。
  3. 使用你的繼承類:一旦你的繼承類建立完成,你可以在基類能夠使用的地方使用你的繼承類,但完成功能就是你自己編寫的了。

繼承類能夠定義在activities裡面,這樣你能夠方便的呼叫,但是這並不是必要的(或許在你的應用程式中你希望建立一個所有人都可以使用的元件)。

完全自定義元件(Fully Customized Components)

完全自定義元件的方法可以建立一些用於顯示的圖形元件(graphical components),也許是一個像電壓表的圖形計量器,或者想卡拉OK裡面顯示歌詞的小球隨著音樂滾動。無論那種方式,你也不能單純的利用元件的結合完成,無論你怎麼結合這些現有的元件。

幸運的是,你可以以你自己的要求輕鬆地建立完全屬於自己的元件,你會發現不夠用的只是你的想象力、螢幕的尺寸和處理器的效能(記住你的應用程式最後只會在那些效能低於桌面電腦的平臺上面執行)。

下面簡單介紹如何打造完全自定義的元件:

  1. 最為通用的VIEW類的父類毫無疑問是View類,因此,最開始你要建立一個基於此類的一個子類。
  2. 你可以寫一個建構函式從XML檔案中提取屬性和引數,當然你也可以自己定義這些屬性和引數(也許是圖形計量器的顏色和尺寸,或者是指標的寬度和幅度等等)
  3. 你可能有必要寫自己的事件監聽器,屬性的訪問和修改函式和一些元件本身的功能上的程式碼。
  4. 如果你希望元件能夠顯示什麼東西,你很有可能會過載 onMeasure() 函式,因而你就不得不過載 onDraw()函式。當兩個函式都用預設的,那麼 onDraw() 函式將不會做任何事情,並且預設的 onMeasure() 函式自動的設定了一個100x100 —的尺寸,這個尺寸可能並不是你想要的。
  5. 其他有必要過載的on... 系列函式都需要重新寫一次。

onDraw()onMeasure()

onDraw()函式將會傳給你一個 Canvas 物件,通過它你可以在二維圖形上做任何事情,包括其他的一些標準和通用的元件、文字的格式,任何你可以想到的東西都可以通過它實現。

注意: 這裡不包括三維圖形如果你想使用三維的圖形,你應該把你的父類由View改為SurfaceView類,並且用一個單獨的執行緒。可以參考GLSurfaceViewActivity 的例子。

onMeasure() 函式有點棘手,因為這個函式是體現元件和容器互動的關鍵部分,onMeasure()應該過載,讓它能夠有效而準確的表現它所包含部分的測量值。這就有點複雜了,因為我們不但要考慮父類的限制(通過onMeasure()傳過來的),同時我們應該知道一旦測量寬度和高度出來後,就要立即呼叫setMeasuredDimension() 方法。

概括的來講,執行onMeasure()函式分為一下幾個階段:

  1. 過載的onMeasure()方法會被呼叫,高度和寬度引數同時也會涉及到(widthMeasureSpecheighMeasureSpec兩個引數都是整數型別),同時你應該考慮你產品的尺寸限制。這裡詳細的內容可以參考View.onMeasure(int, int) (這個連線內容詳細的解釋了整個measurement操作)。
  2. 你的元件要通過onMeasure()計算得到必要的measurement長度和寬度從而來顯示你的元件,它應該與規格保持一致,儘管它可以實現一些規格以外的功能(在這個例子裡,父類能夠選擇做什麼,包括剪下、滑動、提交異常或者用不同的引數又一次呼叫onMeasure()函式)。
  3. 一旦高度和寬度計算出來之後,必須呼叫setMeasuredDimension(int width, int height),否則就會導致異常。

一個自定義元件的例子(A Customized Component Example)

在 API Demos 中的CustomView提供了以一個自定義元件的例子,這個自定義元件在 LabelView 類中定義。

LabelView例子涉及到了自定義元件的方方面面:

  • 首先讓自定義元件從View類中派生出來。
  • 編寫帶引數的建構函式(引數可以來源於XML檔案)。這裡面的一些處理都已經在View父類中完成,但是任然有些Labelview使用的自定義元件特有的新的引數需要處理。
  • 一些標準的Public函式,例如setText()setTextSize()setTextColor()
  • 過載onMeasure()方法來確定元件的尺寸(注意:在LabelView中是通過一個私有函式measureWidth()來實現的)
  • 過載onDraw()函式把Lable顯示在提供的canvas上。

在例子中,你可以通過custom_view_1.xml看到自定義元件LabelView的用法。在XML檔案中特別要注意的是android:app:兩個引數的混合運用,app:參數列示應用程式中被認為是LabelView元件的個體,這些也會作為資源在R類中定義。

元件混合技術Compound Components (or Compound Controls)

如果你不想建立一個完全自定義的元件,而是由幾個現有元件的組合產生的新的元件,那麼混合元件技術就更加適合。簡單的來說,這樣把幾個現有的元件融合到一個邏輯組合裡面可以封裝成一個新的元件。例如,一個Combo Box元件可以看作是是一個EditText和一個帶有彈出列表的Button元件的混合體。如果你點選按鈕為列表選擇一項,

在Android中,其實還有其他的兩個View類可以做到類似的效果: SpinnerAutoCompleteTextView,,但是Combo Box作為一個例子更容易讓人理解。

下面簡單的介紹如何建立組合元件:

  1. 一般從Layout類開始,建立一個Layout類的派生類。也許在Combo box我們會選擇水平方向的LinearLayout作為父類。記住,其他的Layout類是可以巢狀到裡面的,因此混合元件可以是任何元件的混合。注意,正如Activity一樣,你既可以使用外部XML檔案來宣告你的元件,也可以巢狀在程式碼中。
  2. 在新的混合元件的建構函式中,首先,呼叫所有的父類的建構函式,傳入對應的引數。然後可以設定你的混合元件的其他的一些方面,在哪建立EditText元件,又在哪建立PopupList元件。注意:你同時也可以在XML檔案中引入一些自己的屬性和引數,這些屬性和引數也可以被你的混合元件所使用。
  3. 你也可以建立時間監聽器去監聽新元件中View類觸發的事件,例如,對List選項單擊事件的監聽,你必須在此時間發生後更新你EditText的值。
  4. 你可能建立自己的一些屬性,帶有訪問和修改方法。例如,允許設定EditText初始值並且提供訪問它的方法。
  5. 在Layout的派生類中,你沒有必要去過載onDraw()onMeasure()方法,因為Layout會有比較好的預設處理。但是,如果你覺得有必要你也可以過載它。
  6. 你也可能過載一些on系列函式,例如通過onKeyDown()的過載,你可以通過按某個鍵去選擇列表中的對應的值。

總之,把Layout類作為基類有下面幾個優點:

  • 正如activity一樣,你也可以通過XML檔案去宣告你的新元件,或者你也可以在程式碼中巢狀。
  • onDraw()函式和onMeasure()函式是沒有必要過載的,兩個函式已經做得很好了。
  • 你可以很快的建立你的混合元件,並且可以像單一元件那樣使用。

混合元件的例子(Examples of Compound Controls)

In the API Demos project 在API Demos工程中,有兩個List類的例子——Example 4和Example 6,裡面的SpeechView元件是從LinearLayout類派生過來,實現顯示演講顯示功能,對應的原始碼是List4.javaList6.java

調整現有元件(Tweaking an Existing Component)

在某些情況下,你可能有更簡單的方法去建立你的元件。如果你應經有了一個非常類似的元件,你所要做的只是簡單的從這個元件派生出你的元件,重在其中一些有必要修改的方法。通過完全自定義元件的方法你也可以同樣的實現,但通過沖View派生產生新的元件,你可以簡單獲取一些已經存在的處理機制,這些很可能是你所想要的,而沒有必要從頭開始。

例如,在SDK中有一個NotePad的例子(NotePad application )。該例子演示了很多Android平臺實用的細節,例如你會學到從EditView派生出能夠自動換行的記事本。這還不是一個完美的例子,因為相比早期的版本來說,這些API已經感變了很多,但它確實說明了一些問題。

如果你還未檢視該程式,現在你就可以在Eclipse中匯入記事本例程(或僅通過提供的連結檢視相應的原始碼)。特別是檢視NoteEditor.java 中的MyEditText的定義。

下面有幾點要注意的地方:

  1. 宣告(The Definition)

    這個類是通過下面一行程式碼來定義的:

    public static class MyEditText extends EditText

    • 它是定義在NoteEditor activity類裡面的,但是它是共有的(public),因此如果有必要,它可以通過NoteEditor.MyEditTextNoteEditor外面來呼叫。
    • 它是static類(靜態類),意味著不會出現所謂的通過父類訪問資料的“虛態方法”, 這樣就使該類成為一個可以不嚴重依賴NoteEditor的單獨類。對於不需要從外部類訪問的內聯類的建立,這是一個很清晰地思路,保證所產生的類很小,並且允許它可以被其他的類方便的呼叫。
    • 它是EditText類的擴充套件,它是我們選擇的用來自定義的父類。當我們完成以後,新的類就可以作為一個普通的EditText來使用。
  2. 類的初始化

    一般來說,父類是首先呼叫的。進一步來說,這不是一個預設的建構函式,而是一個帶引數的建構函式。因為EditText是使用從XML佈局檔案提取出來的引數進行建立,因此我們的建構函式也要取出引數並且將這些引數傳遞給父類。

  3. 方法過載

    在本例中,僅對onDraw()一個方法進行過載。但你可以很容易地為你的定製元件過載其他需要的方法。

    對於記事本例子來說,通過過載onDraw()方法我們可以在EidtView的畫布(canvas)上繪製藍色的線條(canvas類是通過重寫的onDraw()方法傳遞)。該函式快要結束時要呼叫super.onDraw()函式。父類的方法是應該呼叫,但是在這個例子裡面,我們是在我們劃好了藍線之後呼叫的。

  4. 使用定製元件

    現在,我們已經有自己定製的元件了,但是應該怎樣使用它呢?在記事本例子中,定製的元件直接在預定義的佈局檔案中使用,讓我們看一看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類來建立的,並且是通過全路徑包來描述的。注意這裡內聯類是通過NoteEditor$MyEditText來表示的,這是Java程式設計中引用內聯類的標準方法。
    • 在定義中的其他屬性和引數將傳遞給定製元件的建構函式,然後才傳到EditText建構函式中,因此這些引數也是你使用EditText元件的引數。注意,這裡你也可以增加你自己的引數,我們將在下面討論這個問題。

這就是你全部需要做的,誠然這是一個簡單的例子。但問題的關鍵是:你的需求有多複雜,那麼你的自定義元件就有多麼複雜。

一個更為複雜的元件可能需要過載更多的on系列函式,並且還要很多特有的函式來充分實現自定義元件的功能。唯一的限制就是你的想象力和你需要元件去執行什麼工作。

現在開始你的元件化之旅吧

如你所見,Android提供了一種精巧而又強大的元件模型,讓你儘可能的完成你的工作。從簡單的元件調整到元件混合,甚至完全自定義元件,靈活的運用這些技術,你應該可以得到一個完全符合你外觀要求的的Android程式

相關文章