原文地址:  http://marshal.easymorse.com/archives/2062

android提供了精巧和有力的組件化模型構(gòu)建用戶的UI部分。主要是基于布局類:View和ViewGroup。在此基礎(chǔ)上,android平 臺提供了大量的預(yù)制的View和ViewGroup子類,即布局(layout)和窗口小部件(widget)。可以用它們構(gòu)建自己的UI。

如果沒有符合你需求的預(yù)制窗口小部件,你可以創(chuàng)建自己的視圖子類。如果只是對已存在的窗口小部件或者布局做小的調(diào)整,只需繼承該類,覆蓋相關(guān)的方法。

創(chuàng)建你自己的View子類可以更精確控制視圖元素的外觀和功能。

  • 可創(chuàng)建完整的自定義渲染視圖類型,比如創(chuàng)建一個2d的控制條;
  • 可將一組視圖組件合成為一個新的單一組件,比如雙選的列表,選擇省和市;
  • 覆蓋EditText組件,比如notepad tutorial中的示例;
  • 捕捉其他事件比如按鍵事件,并執(zhí)行自定義的處理方式,比如在游戲中。


基本方法

總的來說,創(chuàng)建自定義的視圖組件步驟是:

  1. 創(chuàng)建自己的類,繼承已經(jīng)存在的View類或者子類;
  2. 覆蓋超類的一些方法。這些超類的方法一般以“on”開頭,比如onDraw()方法等等;
  3. 使用新創(chuàng)建的擴(kuò)展類。一旦完成,你的新擴(kuò)展類就可以用于所有View使用的地方。

注意:擴(kuò)展類可以定義為內(nèi)部類,在你創(chuàng)建的Activity類之中。這很有用,因為這樣可以控制外界的訪問,但是這不是必須的,因為你可能需要一個public的自定義View類供更廣泛的使用。

完全自定義組件

完全自定義的組件可以創(chuàng)建圖形組件顯示在你需要的任何地方。

步驟如下:

  1. 可以繼承的最通用的視圖類是View,可以繼承它創(chuàng)建自定義的組件超類;
  2. 可以提供構(gòu)造方法,并通過xml文件獲取屬性值和參數(shù);
  3. 創(chuàng)建自己的事件監(jiān)聽器,屬性訪問器和編輯器等等;
  4. 一般情況下會覆蓋onMeasure()方法和onDraw()方法,這會讓組件顯示一些東西。如果都用默認(rèn)的行為,onDraw()方法不做任何事情,onMeasure()方法設(shè)置一個100×100的區(qū)域;
  5. 根據(jù)需求覆蓋其他on…方法。

擴(kuò)展onDraw()和onMeasure()方法

onDraw()方法提供給你一個Canvas對象,在它之上可以實現(xiàn)任何你想要的東西,通過2d圖形api。比如其他標(biāo)準(zhǔn)的后者自定義的組件,風(fēng)格化的文字后者其他。

注意:這里不提供3d圖形api的支持。如果你需要3d圖形支持,必須繼承SurfaceView而不是View,并且通過單獨(dú)的線程畫圖。可以通過GLSurfaceViewActivity實例查看詳細(xì)信息。

onMeasure()方法有些麻煩。該方法是在容器和自定義組件之間渲染的重要部分。該方法覆蓋,要高效率的和精確的報告被包含區(qū)域的測量值。

總的來看,實現(xiàn)onMeasure()方法類似如下步驟:

  1. 調(diào)用已經(jīng)覆蓋的onMeasure()方法,傳遞長和寬規(guī)范參數(shù);
  2. 自定義組件在onMeasure()方法中計算需要渲染的組件的長和寬,應(yīng)該在規(guī)范參數(shù)的范圍內(nèi);
  3. 一旦長和寬計算出來,必須調(diào)用setMeasuredDimension(int width, int height)方法,這步失敗會導(dǎo)致異常的拋出。

一個自定義視圖的示例

自定義視圖的示例,見:LabelView

該示例演示了一些自定義組件的不同方面:

  • 繼承View類,用于完全自定義組件;
  • 參數(shù)化的構(gòu)造方法,提供更多的參數(shù),定義在xml文件中;
  • 標(biāo)準(zhǔn)的公開方法,用于設(shè)置標(biāo)簽,比如setText()方法等;
  • 覆蓋onMeasure方法確定渲染的組件尺寸;
  • 覆蓋onDraw方法,在提供的canvas中畫標(biāo)簽。

可以找到對示例的一些使用,在custom_view_1.xml文件中。

該示例運(yùn)行效果:


android示例是混在一起的,比較亂,我這里改寫了一下,只有相關(guān)示例的代碼和配置。看起來比較簡單:

http://easymorse.googlecode.com/svn/tags/android.customer.view.demo_1.0


合成控制器

合成控制器,即不是完全自定義一個新的視圖組件,而是,將現(xiàn)有的原子級控制器(控件?)或者視圖組件組合在一起,處理共同的業(yè)務(wù)邏輯。比如,一個combo box可以被看做,一個單行的EditText和一個相鄰的按鈕,帶一個彈出列表。

在android中還有很多其他的示例,比如Spinner,AutoComleteTextView。

創(chuàng)建合成組件的步驟:

  1. 通常的起始步驟是,創(chuàng)建某種類型的Layout,即創(chuàng)建一個類繼承一個Layout。比如上述的combo box,可能會使用到基于垂直布局的LinearLayout。其他布局也可以嵌套在其中,因此合成組件可以任意復(fù)雜結(jié)構(gòu)。和activity類似,你可 以用基于xml的聲明方式創(chuàng)建容器組件,也可以嵌入到程序代碼中;
  2. 在新類的構(gòu)造方法中,得到超類所需的參數(shù),并傳遞給超類的構(gòu)造方法。另外,也可設(shè)置其他在這個心組件當(dāng)中的視圖組件,比如創(chuàng)建一個EditText和PopupList。注意,你也可以引入自己的參數(shù)和屬性到xml文件中,這樣會被取出并用于你的構(gòu)造方法;
  3. 還可以創(chuàng)建事件監(jiān)聽器,用于容器中的視圖組件,比如一個監(jiān)聽器方法,用于處理列表點(diǎn)擊的監(jiān)聽器,更新EditText的文本內(nèi)容;
  4. 創(chuàng)建自己的屬性訪問器和編輯器,比如,EditText的值可以在組件中初始設(shè)置,并能在需要的時候獲取它的值;
  5. 在繼承Layout類時,不需要覆蓋onDraw()和onMeasure()方法,因為它們可能已經(jīng)符合你的要求,當(dāng)然,也可以覆蓋它們實現(xiàn)自己特定的需求;
  6. 可能需要覆蓋其他on…方法,比如onKeyDown()方法。

總之,使用Layout作為基礎(chǔ)合成自定義的控件,有一些優(yōu)點(diǎn):

  • 可以通過xml文件的方式聲明指定的布局,和activity類似,或者可以通過編程的方式嵌入到你的代碼中;
  • onDraw()方法和onMeasure()等一般可適合需求,因此不必一定要覆蓋它們;
  • 可以快速的構(gòu)建任何復(fù)雜的合成視圖,重用它們?yōu)橐粋€單一的組件。

合成控件的示例

在ApiDemos示例中,演示了SpeechView,它繼承了LinearLayout,并創(chuàng)建了一個組件,用于顯示談話中的引號。相關(guān)的類見:

samples/ApiDemos/src/com/example/android/apis/view/List4.java

samples/ApiDemos/src/com/example/android/apis/view/List6.java

List4示例截圖,見:

List6示例截圖,可以點(diǎn)擊條目,出現(xiàn)內(nèi)容,見:

修改已存在的視圖類型

如果已存在的視圖組件已經(jīng)和你的需求相差不遠(yuǎn),你可以只是簡單的擴(kuò)展該組件,只覆蓋需要改變的行為。

比如示例中的NotePad應(yīng)用(platforms/android-1.5/samples/NotePad)。

效果如下:


在文本框視圖組件(EditText)基礎(chǔ)上,增加了橫線。

本文主要參考:

http://developer.android.com/guide/topics/ui/custom-components.html