- Android應(yīng)用程序開發(fā)與典型案例
- 鄭萌 趙常松等編著
- 749字
- 2018-12-27 18:22:16
第5章 用戶界面開發(fā)
在上一章的學(xué)習(xí)中,主要了解了Android系統(tǒng)的進(jìn)程優(yōu)先級(jí)排序、不同優(yōu)先級(jí)進(jìn)程之間的變化方式,Android系統(tǒng)的4大基本組件及其用途,Activity的生命周期中各個(gè)狀態(tài)及狀態(tài)間的變化關(guān)系、Android應(yīng)用程序的調(diào)試方法和工具。在此基礎(chǔ)上,本章將對(duì)Android程序界面開發(fā)的學(xué)習(xí),包括用戶界面基礎(chǔ)、用戶界面的控件的使用、界面布局的特點(diǎn)及使用方法、菜單的使用方法、界面事件的處理方法等。
5.1 用戶界面基礎(chǔ)
用戶界面(User Interface,UI)是系統(tǒng)和用戶之間進(jìn)行信息交換的媒介,實(shí)現(xiàn)信息的內(nèi)部形式與用戶可以接收形式之間的轉(zhuǎn)換。
在Android系統(tǒng)中,Android自帶有許多要求,預(yù)示著其用戶界面的復(fù)雜性:它是一個(gè)支持多個(gè)并發(fā)應(yīng)用程序的多處理系統(tǒng),接受多種形式的輸入,有著高交互性,必須具有足夠的靈活性,以支持現(xiàn)在和未來廣泛的設(shè)備。令人印象深刻的是豐富的用戶界面及其易用性,實(shí)現(xiàn)了所有給定的功能。但為了使用應(yīng)用程序在不同的設(shè)備上正常的顯示以及運(yùn)行,避免對(duì)系統(tǒng)性能造成過大的負(fù)擔(dān),應(yīng)該明白其工作原理。
Android使用XML文件描述用戶界面;資源文件獨(dú)立保存在資源文件夾中;對(duì)用戶界面描述非常靈活,允許不明確定義界面元素的位置和尺寸,僅聲明界面元素的相對(duì)位置和粗略尺寸。以下就來介紹一下Android的用戶界面框架。
Android是在Java環(huán)境中增加了一個(gè)圖形用戶界面(GUI)工具包,聯(lián)合了AWT,Swing,SWT,和J2ME(撇開Web UI的工具包)。Android框架和他們一樣,它是單線程的,事件驅(qū)動(dòng)的,并建立一個(gè)嵌套的組件庫。
Android用戶界面框架(Android UI Framework),像其他的UI框架一樣,采用了MVC(Model-View-Controller)模型,提供了處理用戶輸入的控制器(Controller),顯示用戶界面和圖像的視圖(View),以及保存數(shù)據(jù)和代碼的模型(Model)。

圖5-1 Android用戶界面框架MVC模型
其中Model是應(yīng)用程序的核心。雖然特定應(yīng)用程序的視圖(View)和控制器(Controller)必然反映他們操縱的Model,但一個(gè)Model可能是由幾個(gè)不同的應(yīng)用使用。想想看,例如,一個(gè)MP3播放器的應(yīng)用程序以及一個(gè)將MP3文件轉(zhuǎn)換成WAV MP3文件的程序,對(duì)于這兩個(gè)應(yīng)用程序,Model包括它的MP3文件格式和編解碼器。然而,前者的應(yīng)用程序,有熟悉的停止,啟動(dòng)和暫??刂频炔僮鳌:笳呖赡懿粫?huì)產(chǎn)生任何聲音;相反,它會(huì)設(shè)置比特率的控制等。此時(shí),他們的Model都是對(duì)所有的文件數(shù)據(jù)。
其中的控制器(Controller)能夠接收并響應(yīng)程序的外部動(dòng)作,如按鍵動(dòng)作或觸摸屏動(dòng)作等??刂破魇褂藐?duì)列處理外部動(dòng)作,每個(gè)外部動(dòng)作作為一個(gè)對(duì)應(yīng)的事件被加入隊(duì)列中,然后Android用戶界面框架按照“先進(jìn)先出”的規(guī)則從隊(duì)列中獲取事件,并將這個(gè)事件分配給所對(duì)應(yīng)的事件處理方法。例如,當(dāng)用戶按下他的手機(jī)上的鍵,Android系統(tǒng)生成的KeyEvent,并將其添加到事件隊(duì)列中。最后,在之前已排隊(duì)的事件被處理后,KeyEvent是從隊(duì)列中刪除的,并作為當(dāng)前選擇View的dispatchKeyEvent方法的調(diào)用參數(shù)傳遞。一旦事件被分派到的焦點(diǎn)組件,該組件可能會(huì)采取適當(dāng)?shù)男袆?dòng)來改變程序的內(nèi)部狀態(tài)。例如,在MP3播放器應(yīng)用程序中,當(dāng)用戶點(diǎn)擊屏幕上的播放/暫停按鈕時(shí),觸發(fā)該按鈕的事件,處理方法可能更新Model,恢復(fù)播放一些先前所選樂曲。
視圖(View)是應(yīng)用程序給用戶的反饋。它負(fù)責(zé)應(yīng)用程序的部分渲染顯示,發(fā)送音頻揚(yáng)聲器,產(chǎn)生觸覺反饋等。視圖部分應(yīng)用視圖樹(View Tree)模型。視圖樹是由Android用戶界面框架中的界面元素以一種樹形結(jié)構(gòu)組織在一起的,Android系統(tǒng)會(huì)依據(jù)視圖樹的結(jié)構(gòu)從上至下繪制每一個(gè)界面元素。每個(gè)元素負(fù)責(zé)對(duì)自身的繪制,如果元素包含子元素,該元素會(huì)通知其下所有子元素進(jìn)行繪制。
下面就來詳細(xì)介紹一下視圖樹。Android當(dāng)中的可視化界面單元,可分為“容器”與“非容器”兩類,容器類繼承ViewGroup,非容器類則從View衍生出來,如圖5-2所示。

圖5-2 Android視圖樹(View Tree)
視圖樹由View和ViewGroup構(gòu)成。其中,View是界面的最基本的可視單元,存儲(chǔ)了屏幕上特定矩形區(qū)域內(nèi)所顯示內(nèi)容的數(shù)據(jù)結(jié)構(gòu),并能夠?qū)崿F(xiàn)所占據(jù)區(qū)域的界面繪制、焦點(diǎn)變化、用戶輸入和界面事件處理等功能。同時(shí)View也是一個(gè)重要的基類,所有在界面上的可見元素都是View的子類。ViewGroup是一種能夠承載含多個(gè)View的顯示單元,它承載了界面布局,同時(shí)還承載了具有原子特性的重構(gòu)模塊。
如圖5-3所示,這些Layout可以套疊式地組成一棵視圖樹。其中,父節(jié)點(diǎn)的Layout與子節(jié)點(diǎn)的LayoutParams之間有控制關(guān)系,例如,若父節(jié)點(diǎn)是RelativeLayout,則子節(jié)點(diǎn)的單元中可以指定RelativeLayout.LayoutParams中的屬性,以控制子節(jié)點(diǎn)在父節(jié)點(diǎn)中的排列狀況。

圖5-3 ViewGroup樹形層次結(jié)構(gòu)
在單線程用戶界面中,控制器從隊(duì)列中獲取事件和視圖在屏幕上繪制用戶界面,使用的都是同一個(gè)線程。這樣的單線程用戶界面使得處理方法具有順序性,能夠降低應(yīng)用程序的復(fù)雜程度,同時(shí)也能降低開發(fā)的難度。
問:?jiǎn)尉€程用戶界面有什么缺點(diǎn)呢?
答:如果事件處理方法過于復(fù)雜,可能會(huì)導(dǎo)致用戶界面失去響應(yīng)。
5.2 界面布局
界面布局(Layout)是用戶界面結(jié)構(gòu)的描述,定義了界面中所有的元素、結(jié)構(gòu)和相互關(guān)系。
界面布局(Layout)是為了適應(yīng)多種Android設(shè)備上的屏幕而設(shè)計(jì)的解決方案:它們可以有不同的像素密度、尺寸和不同的縱橫比。典型的Android設(shè)備,如HTC G1手機(jī),甚至允許應(yīng)用程序運(yùn)行時(shí)改變屏幕的方向(縱向或橫向),因此布局的基礎(chǔ)設(shè)施需要能夠應(yīng)對(duì)這種情況。布局的目的是為開發(fā)人員提供一種方式來表示View之間的物理關(guān)系,因?yàn)樗鼈兪窃谄聊簧侠L制。作為Android的界面布局,它使用開發(fā)需求來滿足與開發(fā)要求最接近的屏幕布局。
Android開發(fā)者使用術(shù)語“布局”,指的是兩種含意中的一種。布局的兩種定義如下。
? 一種資源,它定義了在屏幕上畫什么。布局資源存儲(chǔ)在應(yīng)用程序的/res/layout資源目錄下的XML文件中。布局資源簡(jiǎn)單地說就是一個(gè)用于用戶界面屏幕,或屏幕的一部分,以及內(nèi)容的模板。
? 一種視圖類,它主要是組織其他控件。這些布局類(LinearLayout,RelativeLayout,TableLayout等)用于在屏幕上顯示子控件,如文本控件、按鈕或圖片。
Eclipse的Android開發(fā)插件包含了一個(gè)很方便的用于設(shè)計(jì)和預(yù)覽布局資源的布局資源設(shè)計(jì)器。這個(gè)工具包括兩個(gè)標(biāo)簽視圖:布局視圖允許你預(yù)覽在不同的屏幕下及對(duì)于每一個(gè)方向控件會(huì)如何展現(xiàn);XML視圖告訴你資源的XML定義。
這里有一些關(guān)于在Eclipse中使用布局資源編輯器的技巧。
? 使用概要(Outline)窗格在你的布局資源中添加和刪除控件。
? 選擇特定的控件(在預(yù)覽或概要窗口)并使用屬性窗格來調(diào)整特定控件的屬性。
? 使用XML標(biāo)簽來直接編輯XML定義。
很重要的是要記住一點(diǎn),Eclipse布局資源編輯器不能完全精確的模擬出布局在最終用戶設(shè)備上的呈現(xiàn)形式。對(duì)此,必須在適當(dāng)配置的模擬器中測(cè)試,更重要的是在目標(biāo)設(shè)備上測(cè)試。而且一些“復(fù)雜”控件,包括標(biāo)簽或視頻查看器,也不能在Eclipse中預(yù)覽。
聲明Android程序的界面布局有兩種方法。
? 使用XML文件描述界面布局。
? 在程序運(yùn)行時(shí)動(dòng)態(tài)添加或修改界面布局。
用戶既可以獨(dú)立使用任何一種聲明界面布局的方式,也可以同時(shí)使用兩種方式。
使用XML文件聲明界面布局有以下3 個(gè)特點(diǎn):將程序的表現(xiàn)層和控制層分離;在后期修改用戶界面時(shí),無須更改程序的源代碼;用戶還能夠通過可視化工具直接看到所設(shè)計(jì)的用戶界面,有利于加快界面設(shè)計(jì)的過程,并且為界面設(shè)計(jì)與開發(fā)帶來極大的便利性。
設(shè)計(jì)程序用戶界面最方便且可維護(hù)的方式是創(chuàng)建XML布局資源。這個(gè)方法極大地簡(jiǎn)化了UI設(shè)計(jì)過程,將許多用戶界面控件的布局,以及控件屬性定義移到XML中,代替了寫代碼。它適應(yīng)了UI設(shè)計(jì)師(更關(guān)心布局)和開發(fā)者(了解Java和實(shí)現(xiàn)應(yīng)用程序功能)潛在的區(qū)別。開發(fā)者依然可以在必要時(shí)動(dòng)態(tài)地改變屏幕內(nèi)容。復(fù)雜控件,像ListView或GridView,通常用程序動(dòng)態(tài)地處理數(shù)據(jù)。
XML布局資源必須存放在項(xiàng)目目錄的/res/layout下。對(duì)于每一屏(與某個(gè)活動(dòng)緊密關(guān)聯(lián))都創(chuàng)建一個(gè)XML布局資源是一個(gè)通用的做法,但這并不是必需的。理論上來說,可以創(chuàng)建一個(gè)XML布局資源并在不同的活動(dòng)中使用它,為屏幕提供不同的數(shù)據(jù)。如果需要的話,也可以分散布局資源并用另外一個(gè)文件包含它們。
現(xiàn)在把注意力轉(zhuǎn)向?qū)M織其他控件很有用的布局控件。Android中Layout的列表,如表5-1所示。
表5-1 Layout分類表

5.2.1 線性布局(LinearLayout)
線性布局是最簡(jiǎn)單的布局之一,它提供了控件水平或者垂直排列的模型。如圖5-4 所示,線性布局中,所有的子元素如果垂直排列,則每行僅包含一個(gè)界面元素;如果水平排列,則每列僅包含一個(gè)界面元素。

圖5-4 線性布局(LinearLayout)效果圖
同時(shí),使用此布局時(shí)可以通過設(shè)置控件的Weight參數(shù)控制各個(gè)控件在容器中的相對(duì)大小。LinearLayout布局的屬性既可以在布局文件(XML)中設(shè)置,也可以通過成員方法進(jìn)行設(shè)置。表5-2給出了LinearLayout常用的屬性及這些屬性的對(duì)應(yīng)設(shè)置方法。
表5-2 LinearLayout常用屬性及對(duì)應(yīng)方法

在線性布局中可使用gravity屬性來設(shè)置控件的對(duì)齊方式,gravity可取的值及說明如表5-3所示。
表5-3 gravity可取的屬性及說明

提示:當(dāng)需要為gravity設(shè)置多個(gè)值時(shí),用“|”分隔即可。
以下用一個(gè)線性布局的例子來加深對(duì)線性布局的理解。
1.創(chuàng)建一個(gè)名為L(zhǎng)inearLayout的Android工程
包名稱是edu.hrbeu.LinearLayout,Activity名稱為L(zhǎng)inearLayout。為了能夠完整體驗(yàn)創(chuàng)建線性布局的過程,我們需要?jiǎng)h除Eclipse自動(dòng)建立的/res/layout/main.xml文件,之后我們將手動(dòng)創(chuàng)建一個(gè)XML布局文件。
2.建立XML線性布局文件
首先,刪除Eclipse自動(dòng)建立的/res/layout/main.xml文件;其次,建立用于顯示垂直排列線性布局的XML文件:右擊/res/layout文件夾,選擇“New”→“File”命令打開新文件建立向?qū)?,文件名為main_vertical.xml,保存位置為L(zhǎng)inearLayout/res/layout,如圖5-5所示。

圖5-5 新建線性布局XML文件
3.編輯XML線性布局文件
打開XML文件編輯器,對(duì)main_vertical.xml文件的代碼做如代碼清單5-1所示的修改。
代碼清單5-1 main_vertical.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> </LinearLayout>
第2行代碼是聲明XML文件的根元素為線性布局;第4、5、6行代碼是在屬性編輯器中修改過的寬度、高度和排列方式的屬性。同樣地,用戶可以在可視化編輯器和屬性編輯器中對(duì)頁面布局進(jìn)行修改,這些修改會(huì)同步地反映在XML文件中。
4.添加控件
將四個(gè)界面控件TextView、EditText、Button、Button先后拖曳到可視化編輯器中,所有控件都自動(dòng)獲取控件名稱,并把該名稱顯示在控件上,如TextView01、EditText01、Button01和Button02。

圖5-6 線性布局添加控件
修改界面控件的屬性如表5-4所示。
表5-4 線性布局控件屬性

打開XML文件編輯器查看main_vertical.xml文件代碼,發(fā)現(xiàn)在屬性編輯器內(nèi)填入的文字已經(jīng)正常寫入XML文件中,如代碼清單5-2中第11、20、25行代碼。
代碼清單5-2 main_vertical.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用戶名: " > </TextView> <EditText android:id="@+id/entry" android:layout_height="wrap_content" android:layout_width="match_parent"> </EditText> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="確認(rèn)"> </Button> <Button android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="取消" > </Button> </LinearLayout>
5.修改LinearLayout.java文件
將LinearLayout.java文件中的setContentView(R.layout.main),更改為setContentView (R.layout.main_vertical)。
同理,按照以上步驟,可以得到橫向線性布局。
? 建立main_ horizontal.xml文件。
? 線性布局的Orientation屬性的值設(shè)置為horizontal。
? 將EditText的Layout width屬性的值設(shè)置為wrap_content。
? 將LinearLayout.java文件中的setContentView(R.layout.main_vertical)修改為setContentView(R.layout.main_ horizontal)。
5.2.2 框架布局(FrameLayout)
框架布局(FrameLayout)是最簡(jiǎn)單的界面布局,它在屏幕上開辟出了一塊區(qū)域,在這塊區(qū)域中可以添加多個(gè)子控件,但是所有的子控件都被對(duì)齊到屏幕的左上角??蚣懿季值拇笮∮勺涌丶谐叽缱畲蟮哪莻€(gè)子控件來決定。如果子控件一樣大,同一時(shí)刻只能看到最上面的子控件。
FrameLayout繼承自ViewGroup,除了繼承自父類的屬性和方法,F(xiàn)rameLayout類中包含了自己特有的屬性和方法,如表5-5所示。
表5-5 FrameLayout常用屬性及對(duì)應(yīng)方法

提示:
在FrameLayout中,子控件是通過棧來繪制的,所以后添加的子控件會(huì)被繪制在上層。
以下用一個(gè)FrameLayout的例子來加深對(duì)FrameLayout的理解。
(1)在Eclipse中新建一個(gè)項(xiàng)目FrameLayout。打開其res/values目錄下的strings.xml,在其中輸入如代碼清單5-3所示代碼。在該段代碼中聲明了應(yīng)用程序總會(huì)用到的字符串資源。
代碼清單5-3 strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">FrameExample</string> <string name="big">大的</string> <string name="middle">中的</string> <string name="small">小的</string> </resources>
(2)在項(xiàng)目rers/values目錄下新建一個(gè)colors.xml,在其中輸入如代碼清單5-4所示代碼。該段代碼聲明了應(yīng)用程序中將會(huì)用到的顏色資源。這樣將所有顏色資源統(tǒng)一管理有助于提高程序的可讀性及可維護(hù)性。
代碼清單5-4 colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="red">#FF0000</color> <color name="green">#00FF00</color> <color name="blue">#0000FF</color> <color name="white">#FFFFFF</color> </resources>
(3)打開項(xiàng)目res/layout目錄下的main.xml文件,將其中已有的代碼替換為如代碼清單5-5所示代碼。
代碼清單5-5 main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout android:id="@+id/FrameLayout01" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" xmlns:android="http://schemas.android.com/apk/res/android">< <TextView android:text="@string/big" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="60px" android:textColor="@color/green" > <!-- 聲明一個(gè)TextView控件 --> </TextView> <TextView android:text="@string/middle" android:id="@+id/TextView02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="40px" android:textColor="@color/red" > <!-- 聲明一個(gè)TextView控件 --> </TextView> <TextView android:text="@string/small" android:id="@+id/TextView03" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20px" android:textColor="@color/blue" > <!-- 聲明一個(gè)TextView控件 --> </TextView> </FrameLayout>
代碼第2~7行聲明了一個(gè)框架布局,并設(shè)置其在父控件中的顯示方式及自身的背景顏色;代碼第8~16行聲明了一個(gè)TextView控件,該控件ID為TextView01,第13行定義了其顯示內(nèi)容的字號(hào)為60px,第14行定義了所顯示內(nèi)容的字體顏色為綠色;代碼第17~25行聲明了一個(gè)TextView控件,該控件ID為TextView02,第22行定義了其顯示內(nèi)容的字號(hào)為40px,第23行定義了所顯示內(nèi)容的字體顏色為紅色;代碼第26~34行聲明了一個(gè)TextView控件,該控件id為TextView03,第22行定義了其顯示內(nèi)容的字號(hào)為20px,第23行定義了所顯示內(nèi)容的字體顏色為藍(lán)色。
運(yùn)行程序,在圖5-7 所示的運(yùn)行效果圖中可以看到,程序運(yùn)行時(shí)所有的子控件都自動(dòng)地對(duì)齊到容器的左上角,由于子控件的TextView是按照字號(hào)從大到小排列的,所以字號(hào)小的在最上層。

圖5-7 框架布局運(yùn)行效果圖
5.2.3 表格布局(TableLayout)
TableLayout類以行和列的形式管理控件,每行為一個(gè)TableRow對(duì)象,也可以為一個(gè)View對(duì)象,當(dāng)為View對(duì)象時(shí),該View對(duì)象將跨越該行的所有列。在TableRow中可以添加子控件,每添加一個(gè)子控件為一列。
TableLayout布局中并不會(huì)為每一行、每一列或每個(gè)單元格繪制邊框,每一行可以有0或多個(gè)單元格,每個(gè)單元格為一個(gè)View對(duì)象。TableLayout中可以有空的單元格,單元格也可以像HTML中那樣跨越多個(gè)列。
圖5-8是表格布局的示意圖。

圖5-8 表格布局示意圖
在表格布局中,一個(gè)列的寬度由該列中最寬的那個(gè)單元格指定,而表格的寬度是由父容器指定的。在TableLayout中,可以為列設(shè)置3種屬性。
? Shrinkable,如果一個(gè)列被標(biāo)識(shí)為shrinkable,則該列的寬度可以進(jìn)行收縮,以使表格能夠適應(yīng)其父容器的大小。
? Stretchable,如果一個(gè)列被標(biāo)識(shí)為stretchable,則該列的寬度可以進(jìn)行拉伸,以填滿表格中空閑的空間。
? Collapsed,如果一個(gè)列被標(biāo)識(shí)為collapsed,則該列將會(huì)被隱藏。
注意:
一個(gè)列可以同時(shí)具有Shrinkable和Stretchable屬性,在這種情況下,該列的寬度將任意拉伸或收縮以適應(yīng)父容器。
TableLayout繼承自LinearLayout類,除了繼承來自父類的屬性和方法,TableLayout類中還包含表格布局所特有的屬性和方法。這些屬性和方法說明如表5-6所示。
表5-6 TableLayout類常用屬性及對(duì)應(yīng)方法說明

以下我們用一個(gè)表格布局的例子來加深對(duì)表格布局的理解。
首先,建立表格布局要注意以下幾點(diǎn)。
(1)向界面中添加一個(gè)表格布局,無須修改布局的屬性值。其中,ID屬性為TableLayout01,Layout width和Layout height屬性都為wrap_content。
(2)向TableLayout01中添加兩個(gè)TableRow。TableRow代表一個(gè)單獨(dú)的行,每行被劃分為幾個(gè)小的單元,單元中可以添加一個(gè)界面控件。其中,ID屬性分別為TableRow01和TableRow02,Layout width和Layout height屬性都為wrap_content。
(3)通過Outline,向TableRow01中添加TextView和EditText;向TableRow02中添加兩個(gè)Button。

圖5-9 向TableRow01中添加TextView和EditText
參考表5-7設(shè)置TableRow中4個(gè)界面控件的屬性值。
表5-7 表格布局控件屬性

(4)main.xml完整代碼如代碼清單5-6所示。
代碼清單5-6 main.xml
<?xml version="1.0" encoding="utf-8"?> <TableLayout android:id="@+id/TableLayout01" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <TableRow android:id="@+id/TableRow01" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/label" android:layout_height="wrap_content" android:layout_width="160dip" android:gravity="right" android:text="用戶名:" android:padding="3dip" > </TextView> <EditText android:id="@+id/entry" android:layout_height="wrap_content" android:layout_width="160dip" android:padding="3dip" > </EditText> </TableRow> <TableRow android:id="@+id/TableRow02" android:layout_width="wrap_content" android:layout_height="wrap_content"> <Button android:id="@+id/ok" android:layout_height="wrap_content" android:padding="3dip" android:text="確認(rèn)"> </Button> <Button android:id="@+id/Button02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="3dip" android:text="取消"> </Button> </TableRow> </TableLayout>
代碼中,第3行代碼使用了<TableLayout>標(biāo)簽聲明表格布局;第7行和第23行代碼聲明了兩個(gè)TableRow元素;第12行設(shè)定寬度屬性android:layout_width:160dip;第13行設(shè)定屬性android:gravity,指定文字為右對(duì)齊;第15行使用屬性android:padding,聲明TextView元素與其他元素的間隔距離為3dip。
(5)表格布局運(yùn)行效果如圖5-10所示。

圖5-10 表格布局運(yùn)行圖
5.2.4 相對(duì)布局(RelativeLayout)
相對(duì)布局(RelativeLayout)是一種非常靈活的布局方式,能夠通過指定界面元素與其他元素的相對(duì)位置關(guān)系,確定界面中所有元素的布局位置,能夠最大限度保證在各種屏幕類型的手機(jī)上正確顯示界面布局。
在相對(duì)布局中,子控件的位置是相對(duì)兄弟控件或父容器而決定的。出于性能考慮,在設(shè)計(jì)相對(duì)布局時(shí)要按照控件之間的依賴關(guān)系排列,如View A的位置相對(duì)于View B來決定,則需要保證在布局文件中View B在View A的前面。
在進(jìn)行相對(duì)布局時(shí)用到的屬性很多,首先來看屬性值只為true或false的屬性,如表5-8所示。
表5-8 相對(duì)布局中只取true或false的屬性及說明

接下來看屬性值為其他控件ID的屬性,如表5-9所示。
表5-9 相對(duì)布局中取值為其他控件ID的屬性及說明

最后要介紹的是屬性值以像素為單位的屬性及說明,如表5-10所示。
表5-10 相對(duì)布局中取值為像素的屬性及說明

需要注意的是在進(jìn)行相對(duì)布局時(shí)要避免出現(xiàn)循環(huán)依賴,例如,設(shè)置相對(duì)布局在父容器中的排列方式為WRAP_CONTENT,就不能再將相對(duì)布局的子控件設(shè)置為ALIGN_PARENT_BOTTOM。因?yàn)檫@樣會(huì)造成子控件和父控件相互依賴和參照的錯(cuò)誤。
以下用一個(gè)相對(duì)布局的例子來加深對(duì)線性布局的理解。首先來看一下相對(duì)布局的效果圖,如圖5-11所示。

圖5-11 相對(duì)布局效果圖
為達(dá)到以上效果,按以下步驟進(jìn)行操作。
(1)添加TextView控件(用戶名),相對(duì)布局會(huì)將TextView控件放置在屏幕的最上方。
(2)添加EditText控件(輸入框),并聲明該控件的位置在TextView控件的下方,相對(duì)布局會(huì)根據(jù)TextView的位置確定EditText控件的位置。
(3)添加第一個(gè)Button控件(“取消”按鈕),聲明在EditText控件的下方,且在父控件的最右邊。
(4)添加第二個(gè)Button控件(“確認(rèn)”按鈕),聲明該控件在第一個(gè)Button控件的左方,且與第一個(gè)Button控件處于相同的水平位置。
相對(duì)布局在main.xml文件的完整代碼如代碼清單5-7所示。
代碼清單5-7 main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/RelativeLayout01" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/label" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="用戶名:"> </TextView> <EditText android:id="@+id/entry" android:layout_height="wrap_content" android:layout_width="match_parent" android:layout_below="@id/label"> </EditText> <Button android:id="@+id/cancel" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_alignParentRight="true" android:layout_marginLeft="10dip" android:layout_below="@id/entry" android:text="取消" > </Button> <Button android:id="@+id/ok" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_toLeftOf="@id/cancel" android:layout_alignTop="@id/cancel" android:text="確認(rèn)">、 </Button> </RelativeLayout>
在代碼中,第3行使用了<RelativeLayout>標(biāo)簽聲明一個(gè)相對(duì)布局;第15行使用位置屬性android:layout_below,確定EditText控件在ID為label的元素下方;第20行使用屬性android:layout_alignParentRight,聲明該元素在其父元素的右邊邊界對(duì)齊;第21行設(shè)定屬性android:layout_marginLeft,左移10dip;第22行聲明該元素在ID為entry的元素下方;第28行聲明使用屬性android:layout_toLeftOf,聲明該元素在ID為cancel元素的左邊;第29行使用屬性android:layout_alignTop,聲明該元素與ID為cancel的元素在相同的水平位置。
5.2.5 絕對(duì)布局(AbsoluteLayout)
絕對(duì)布局(AbsoluteLayout)能通過指定界面元素的坐標(biāo)位置,來確定用戶界面的整體布局。所謂絕對(duì)布局,是指屏幕中所有控件的擺放由開發(fā)人員通過設(shè)置控件的坐標(biāo)來指定,控件容器不再負(fù)責(zé)管理其子控件的位置。由于子控件的位置和布局都通過坐標(biāo)來指定,因此AbsoluteLayout類中并沒有開發(fā)特有的屬性和方法。
絕對(duì)布局是一種不推薦使用的界面布局,因?yàn)橥ㄟ^X軸和Y軸確定界面元素位置后,Android系統(tǒng)不能夠根據(jù)不同屏幕對(duì)界面元素的位置進(jìn)行調(diào)整,降低了界面布局對(duì)不同類型和尺寸屏幕的適應(yīng)能力。每一個(gè)界面控件都必須指定坐標(biāo)(X,Y),例如圖5-12 中,“確認(rèn)”按鈕的坐標(biāo)是(40,120),“取消”按鈕的坐標(biāo)是(120,120)。坐標(biāo)原點(diǎn)(0,0)在屏幕的左上角。

圖5-12 絕對(duì)布局效果圖
絕對(duì)布局示例在main.xml文件的完整代碼如代碼清單5-8所示。
代碼清單5-8 main.xml
<?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout android:id="@+id/AbsoluteLayout01" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/label" android:layout_x="40dip" android:layout_y="40dip" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="用戶名:"> </TextView> <EditText android:id="@+id/entry" android:layout_x="40dip" android:layout_y="60dip" android:layout_height="wrap_content" android:layout_width="150dip"> </EditText> <Button android:id="@+id/ok" android:layout_width="70dip" android:layout_height="wrap_content" android:layout_x="40dip" android:layout_y="120dip" android:text="確認(rèn)"> </Button> <Button android:id="@+id/cancel" android:layout_width="70dip" android:layout_height="wrap_content" android:layout_x="120dip" android:layout_y="120dip" android:text="取消"> </Button> </AbsoluteLayout>
上述涉及的界面布局(LinearLayout,TableLayout,RelativeLayout等)像其他控件一樣也是一個(gè)控件。這意味著布局控件可以被嵌套。比如,為了組織屏幕上的控件你可以在一個(gè)LinearLayout中使用一個(gè)RelativeLayout,反過來也行。但是需注意在界面設(shè)計(jì)過程中,盡量保證屏幕相對(duì)簡(jiǎn)單,復(fù)雜布局加載很慢并且可能引起性能問題。
同時(shí),在設(shè)計(jì)程序布局資源時(shí)需要考慮設(shè)備的差異性。通常情況下是可能設(shè)計(jì)出在各種不同設(shè)備上看著都不錯(cuò)的靈活布局的,不管是豎屏還是模屏模式。必要時(shí)可以引入可選布局資源來處理特殊情況。例如,可以根據(jù)設(shè)備的方向或設(shè)備是不是有超大屏幕(如網(wǎng)絡(luò)平板)來提供不同的布局供加載。
Android SDK提供了幾個(gè)可以幫助我們?cè)O(shè)計(jì)、調(diào)試和優(yōu)化布局資源的工具。除了Eclipse的Android插件中內(nèi)置的布局資源設(shè)計(jì)器,還可以使用Android SDK提供的Hierarchy Viewer(層次結(jié)構(gòu)查看器)和layoutopt。這些工具在Android SDK的/tools目錄下可以找到。可以使用Hierarchy Viewer來查看布局運(yùn)行時(shí)的詳細(xì)情況;可以使用layoutopt(布局優(yōu)化)命令行工具來優(yōu)化你的布局文件。優(yōu)化布局非常重要,因?yàn)閺?fù)雜的布局文件加載很慢。layoutopt工具簡(jiǎn)單地掃描XML布局文件并找出不必要的控件。在Android開發(fā)者網(wǎng)站的layoutopt部分查看更多信息。
5.3 界面控件
Android系統(tǒng)的界面控件分為定制控件和系統(tǒng)控件。
定制控件是用戶獨(dú)立開發(fā)的控件,或通過繼承并修改系統(tǒng)控件后所產(chǎn)生的新控件。能夠?yàn)橛脩籼峁┨厥獾墓δ芑蚺c眾不同的顯示需求方式;系統(tǒng)控件是Android系統(tǒng)提供給用戶已經(jīng)封裝的界面控件,它提供應(yīng)用程序開發(fā)過程中常見功能控件。同時(shí),系統(tǒng)控件更有利于幫助用戶進(jìn)行快速開發(fā),能夠使Android系統(tǒng)中應(yīng)用程序的界面保持一致性。
這里著重講解一下系統(tǒng)控件的使用。
常見的系統(tǒng)控件包括TextView、EditText、Button、ImageButton、Checkbox、RadioButton、Spinner、ListView和TabHost。
5.3.1 TextView和EditText
TextView是一種用于顯示字符串的控件;EditText則是用來輸入和編輯字符串的控件,它是一個(gè)具有編輯功能的TextView。
每個(gè)TextView期望的這樣一個(gè)組件的屬性:可以改變它的高度、寬度、字體、文字顏色、背景顏色等。TextView也有一些有用的獨(dú)特屬性,如表5-11所示。
表5-11 TextView也有一些有用的獨(dú)特屬性

下面就通過一個(gè)例子來加深對(duì)這兩個(gè)控件的理解。
首先,建立一個(gè)“TextViewDemo”的程序,包含TextView和EditText兩個(gè)控件,如圖5-13所示。上方“用戶名”部分使用的是TextView,下方的文字輸入框使用的是EditText。

圖5-13 TextView與EditView效果圖
TextViewDemo在XML文件中的代碼如代碼清單5-9所示。
代碼清單5-9 main.xml
<TextView android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView01" > </TextView> <EditText android:id="@+id/EditText01" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="EditText01" > </EditText>
在上述代碼中,第1行android:id屬性聲明了TextView的ID,這個(gè)ID主要用于在代碼中引用這個(gè)TextView對(duì)象;“@+id/TextView01”表示所設(shè)置的ID值;@表示后面的字符串是ID資源;加號(hào)(+)表示需要建立新資源名稱,并添加到R.java文件中;斜杠后面的字符串(TextView01)表示新資源的名稱;如果資源不是新添加的,或?qū)儆贏ndroid框架的ID資源,則不需要使用加號(hào)(+),對(duì)于Android框架中的ID資源,還必須添加Android包的命名空間,如android:id="@android:id/empty"。
第2行的android:layout_width屬性用來設(shè)置TextView的寬度,wrap_content表示TextView的寬度只要能夠包含所顯示的字符串即可。
第3行的android:layout_height屬性用來設(shè)置TextView的高度。
第4行表示TextView所顯示的字符串,在后面將通過代碼更改TextView的顯示內(nèi)容。
第7行中“fill_content”表示EditText的寬度將等于父控件的寬度。
在上述步驟之后,修改TextViewDemo.java文件中代碼為代碼清單5-10所示的代碼:
代碼清單5-10 TextViewDemo.java
TextView textView = (TextView)findViewById(R.id.TextView01); EditText editText = (EditText)findViewById(R.id.EditText01); textView.setText("用戶名:"); editText.setText("");
第1行代碼的findViewById()方法能夠通過ID引用界面上的任何控件,只要該控件在XML文件中定義過ID即可。
第3行代碼的setText()方法用來設(shè)置TextView所顯示的內(nèi)容。
5.3.2 Button和ImageButton
Button是一種按鈕控件,用戶能夠在該控件上點(diǎn)擊,并后引發(fā)相應(yīng)的事件處理方法;ImageButton用以實(shí)現(xiàn)能夠顯示圖像功能的控件按鈕。
下面通過一個(gè)例子來加深對(duì)這兩個(gè)控件的理解。
1.建立一個(gè)“ButtonDemo”的程序
程序包含Button和ImageButton兩個(gè)按鈕,上方是“Button按鈕”,下方是一個(gè)ImageButton控件,如圖5-14所示。

圖5-14 Button與ImageButton效果圖
ButtonDemo在XML文件中的代碼如代碼清單5-11所示。
代碼清單5-11 main.xml
<Button android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button01" > </Button> <ImageButton android:id="@+id/ImageButton01" android:layout_width="wrap_content" android:layout_height="wrap_content"> </ImageButton>
在上述代碼中,定義Button控件的高度、寬度和內(nèi)容及ImageButton控件的高度和寬度,但是沒定義顯示的圖像,在后面的代碼中進(jìn)行定義。
2.引入資源
將download.png文件復(fù)制到/res/drawable文件夾下,在/res目錄上選擇Refresh,就可以看到新添加的文件顯示在/res/drawable文件夾下,同時(shí)R.java文件內(nèi)容也得到了更新,否則提示無法找到資源的錯(cuò)誤。
3.更改Button和ImageButton內(nèi)容
在ButtonDemo.java中引入android.widget.Button和android.widget.ImageButton,并修改其代碼如代碼清單5-12所示。
代碼清單5-12 ButtonDemo.java
Button button = (Button)findViewById(R.id.Button01); ImageButton imageButton = (ImageButton)findViewById(R.id.ImageButton01); button.setText("Button按鈕"); imageButton.setImageResource(R.drawable.download);
上述代碼中,第1行代碼用于引用在XML文件中定義的Button控件。
第2行代碼用于引用在XML文件中定義的ImageButton控件。
第3行代碼將Button的顯示內(nèi)容更改為“Button按鈕”。
第4行代碼利用setImageResource()方法,將新加入的png文件R.drawable.download傳遞給ImageButton。
4.按鈕響應(yīng)點(diǎn)擊事件:添加點(diǎn)擊事件的監(jiān)聽器
在ButtonDemo.java中添加代碼清單5-13所示的代碼。
代碼清單5-13 ButtonDemo.java
final TextView textView = (TextView)findViewById(R.id.TextView01); button.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { textView.setText("Button按鈕"); } }); imageButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { textView.setText("ImageButton按鈕"); } });
在本段代碼中,第2行代碼中button對(duì)象通過調(diào)用setOnClickListener()方法,注冊(cè)一個(gè)點(diǎn)擊(Click)事件的監(jiān)聽器View.OnClickListener()。
第3行代碼是點(diǎn)擊事件的回調(diào)方法。
第4行代碼將TextView的顯示內(nèi)容更改為“Button按鈕”。
這里我們來了解一下View.OnClickListener()。
View.OnClickListener()是View定義的點(diǎn)擊事件的監(jiān)聽器接口,并在接口中僅定義了onClick()方法。當(dāng)Button從Android界面框架中接收到事件后,首先檢查這個(gè)事件是否是點(diǎn)擊事件,如果是點(diǎn)擊事件,同時(shí)Button又注冊(cè)了監(jiān)聽器,則會(huì)調(diào)用該監(jiān)聽器中的onClick()方法。每個(gè)View僅可以注冊(cè)一個(gè)點(diǎn)擊事件的監(jiān)聽器,如果使用setOnClickListener()方法注冊(cè)第二個(gè)點(diǎn)擊事件的監(jiān)聽器,之前注冊(cè)的監(jiān)聽器將被自動(dòng)注銷。
多個(gè)按鈕注冊(cè)到同一個(gè)點(diǎn)擊事件的監(jiān)聽器上,代碼如代碼清單5-14所示。
代碼清單5-14多個(gè)按鈕注冊(cè)到一個(gè)點(diǎn)擊事件的監(jiān)聽器上
Button.OnClickListener buttonListener = new Button.OnClickListener(){ @Override public void onClick(View v) { switch(v.getId()){ case R.id.Button01: textView.setText("Button按鈕"); return; case R.id.ImageButton01: textView.setText("ImageButton按鈕"); return; } }}; Button.setOnClickListener(buttonListener); ImageButton.setOnClickListener(buttonListener);
該段代碼中,第1行至第12行代碼定義了一個(gè)名為buttonListener的點(diǎn)擊事件監(jiān)聽器;第13行代碼將該監(jiān)聽器注冊(cè)到Button上;第14行代碼將該監(jiān)聽器注冊(cè)到ImageButton上。
5.3.3 CheckBox和RadioButton
CheckBox是一個(gè)同時(shí)可以選擇多個(gè)選項(xiàng)的控件;而RadioButton則是僅可以選擇一個(gè)選項(xiàng)的控件;RadioGroup是RadioButton的承載體,程序運(yùn)行時(shí)不可見,應(yīng)用程序中可能包含一個(gè)或多個(gè)RadioGroup,一個(gè)RadioGroup包含多個(gè)RadioButton,在每個(gè)RadioGroup中,用戶僅能夠選擇其中一個(gè)RadioButton。
下面就通過一個(gè)例子來加深對(duì)這兩個(gè)控件的理解,其效果如圖5-15所示。

圖5-15 CheckBox與RadioButton效果圖
1.建立一個(gè)“CheckboxRadiobuttonDemo”程序
程序包含5 個(gè)控件,從上至下分別是TextView01、CheckBox01、 CheckBox02、RadioButton01、RadioButton02,當(dāng)選擇RadioButton01時(shí),RadioButton02則無法選擇。
CheckboxRadiobuttonDemo在XML文件中的代碼如代碼清單5-15所示。
代碼清單5-15 main.xml
<TextView android:id="@+id/TextView01“ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello"/> <CheckBox android:id="@+id/CheckBox01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CheckBox01" > </CheckBox> <CheckBox android:id="@+id/CheckBox02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CheckBox02" > </CheckBox> <RadioGroup android:id="@+id/RadioGroup01" android:layout_width="wrap_content" android:layout_height="wrap_content"> <RadioButton android:id="@+id/RadioButton01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="RadioButton01" > </RadioButton> <RadioButton android:id="@+id/RadioButton02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="RadioButton02" > </RadioButton> </RadioGroup>
上述代碼中,第15行<RadioGroup>標(biāo)簽聲明了一個(gè)RadioGroup;在第18行和第23行分別聲明了兩個(gè)RadioButton,這兩個(gè)RadioButton是RadioGroup的子元素。
2.引用CheckBox和RadioButton
引用CheckBox和RadioButton的方法參考代碼清單5-16所示的代碼。
代碼清單5-16引用CheckBox和RadioButton
CheckBox checkBox1= (CheckBox)findViewById(R.id.CheckBox01); RadioButton radioButton1 =(RadioButton)findViewById(R.id.RadioButton01);
3.響應(yīng)點(diǎn)擊事件:添加點(diǎn)擊事件的監(jiān)聽器
CheckBox設(shè)置點(diǎn)擊事件監(jiān)聽器的方法與Button設(shè)置點(diǎn)擊事件監(jiān)聽器中介紹的方法相似,唯一不同在于將Button.OnClickListener換成了CheckBox.OnClickListener。
代碼清單5-17設(shè)置CheckBox點(diǎn)擊事件監(jiān)聽器
CheckBox.OnClickListener checkboxListener = new CheckBox.OnClickListener(){ @Override public void onClick(View v) { //過程代碼 }}; checkBox1.setOnClickListener(checkboxListener); checkBox2.setOnClickListener(checkboxListener);
RadioButton設(shè)置點(diǎn)擊事件監(jiān)聽器的方法如代碼清單5-18所示。
代碼清單5-18設(shè)置RadioButton點(diǎn)擊事件監(jiān)聽器
RadioButton.OnClickListener radioButtonListener = new RadioButton.OnClickListener(){ @Override public void onClick(View v) { //過程代碼 }}; radioButton1.setOnClickListener(radioButtonListener); radioButton2.setOnClickListener(radioButtonListener);
通過上述的講解,可以得出這樣的結(jié)論:CheckBox是可以選擇多個(gè)選項(xiàng)的復(fù)選框控件,當(dāng)其中選項(xiàng)被選中時(shí),顯示相應(yīng)的checkmark。這時(shí),需要?jiǎng)?chuàng)建一個(gè)“OnClickListener”捕獲點(diǎn)擊事件,并可以添加所需的功能代碼。
RadioGroup是一個(gè)包含一些RadioButton的ViewGroup。用戶可選擇一個(gè)按鈕,通過對(duì)每一個(gè)RadioButton設(shè)置監(jiān)聽OnClickListeners來獲取其選擇。這里需注意,點(diǎn)擊RadioButton并不觸發(fā)RadioGroup的Click事件。
5.3.4 Spinner
Spinner是一種能夠從多個(gè)選項(xiàng)中選擇選項(xiàng)的控件,類似于桌面程序的組合框(ComboBox),但沒有組合框的下拉菜單,而是使用浮動(dòng)菜單為用戶提供選擇,如圖5-16所示。

圖5-16 Spinner效果圖
下面就通過一個(gè)例子來加深對(duì)Spinner的理解。
1.建立一個(gè)程序“SpinnerDemo”
程序包含3個(gè)子項(xiàng),Spinner控件在XML文件中的代碼如代碼清單5-19所示。
代碼清單5-19 main.xml
<TextView android:id="@+id/TextView01" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello"/> <Spinner android:id="@+id/Spinner01" android:layout_width="300dip" android:layout_height="wrap_content"> </Spinner>
在上述代碼中,第5行使用<Spinner>標(biāo)簽聲明了一個(gè)Spinner控件;第6行代碼中指定了該控件的寬度為“300dip”。
2.修改SpinnerDemo.java文件
在SpinnerDemo.java文件中,定義一個(gè)ArrayAdapter適配器,在ArrayAdapter中添加Spinner的內(nèi)容,需要在代碼中引入android.widget.ArrayAdapter和android.widget.Spinner。
代碼清單5-20 SpinnerDemo.java
Spinner spinner = (Spinner) findViewById(R.id.Spinner01); List<String> list = new ArrayList<String>(); list .add("Spinner子項(xiàng)1"); list .add("Spinner子項(xiàng)2"); list .add("Spinner子項(xiàng)3"); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter);
本段代碼中,第2行代碼建立了一個(gè)字符串?dāng)?shù)組列表(ArrayList),這種數(shù)組列表可以根據(jù)需要進(jìn)行增減,<String>表示數(shù)組列表中保存的是字符串類型的數(shù)據(jù)。
在代碼的第3、4、5行中,使用add()方法分別向數(shù)組列表中添加3個(gè)字符串。
第6行代碼建立了一個(gè)ArrayAdapter的數(shù)組適配器,數(shù)組適配器能夠?qū)⒔缑婵丶偷讓訑?shù)據(jù)綁定在一起。
第7行代碼設(shè)定了Spinner的浮動(dòng)菜單的顯示方式,其中,android.R.layout.simple_spinner_dropdown_item是Android系統(tǒng)內(nèi)置的一種浮動(dòng)菜單。
第8行代碼實(shí)現(xiàn)綁定過程,所有ArrayList中的數(shù)據(jù),將顯示在Spinner的浮動(dòng)菜單中。
利用該段代碼,適配器綁定界面控件和底層數(shù)據(jù),如果底層數(shù)據(jù)更改了,用戶界面也相應(yīng)修改顯示內(nèi)容,因此不需要應(yīng)用程序再監(jiān)視,從而極大地簡(jiǎn)化了代碼的復(fù)雜性。
由上述例子可以得出結(jié)論:與上一小節(jié)中的CheckBox和RadioButton相比,Sipnner需要的工作量最大,但可以為用戶提供相對(duì)來說較好的屏幕顯示。如上所示,Spinner顯示當(dāng)前選中的選項(xiàng),當(dāng)單擊右側(cè)的下拉列表時(shí),彈出一個(gè)可供選擇的選項(xiàng)列表。為了實(shí)現(xiàn)該功能需滿足以下條件。
(1)創(chuàng)建一個(gè)可供選擇的選項(xiàng)列表(該列表可以是動(dòng)態(tài)創(chuàng)建并被應(yīng)用程序修改)。
(2)為Spinner的列表創(chuàng)建一個(gè)ArrayAdapter以實(shí)現(xiàn)其下拉列表的顯示。這里需注意ArrayAdapter的格式(simple_spinner_item和simple_spinner_dropdown_item)是由Android系統(tǒng)定義的,它們不會(huì)出現(xiàn)在資源XML文件中。
(3)創(chuàng)建onItemSelectedListener來捕捉Spinner的選擇事件。監(jiān)聽onItemSelected Listener包含onItemSelected()方法和onNothingSelected()方法。
5.3.5 ListView
ListView是一種用于垂直顯示的列表控件,如果顯示內(nèi)容過多,則會(huì)出現(xiàn)垂直滾動(dòng)條。
ListView能夠通過適配器將數(shù)據(jù)和自身綁定,在有限的屏幕上提供大量?jī)?nèi)容供用戶選擇,所以是經(jīng)常使用的用戶界面控件。同時(shí),ListView支持點(diǎn)擊事件處理,用戶可以用少量的代碼實(shí)現(xiàn)復(fù)雜的選擇功能。例如,調(diào)用setAdapter()提供的數(shù)據(jù)和View子項(xiàng),并通過setOnItemSelectedListener()方法監(jiān)聽ListView上子項(xiàng)選擇事件。
若Activity由一個(gè)單一的列表控制,則Activity需繼承ListActivity類而不是之前介紹的常規(guī)的Activity類。如果主視圖僅僅只是列表,甚至不需要建立一個(gè)layout,ListActivity會(huì)為用戶構(gòu)建一個(gè)全屏幕的列表。如果想自定義布局,則需要確定ListView的id為@android:id/list,以便ListActivity知道其Activity的主要清單。
下面就通過一個(gè)例子來加深對(duì)ListView的理解,如圖5-17所示。

圖5-17 ListView效果圖
1.建立一個(gè)“ListViewDemo”程序
XML文件中的代碼如代碼清單5-21所示。
代碼清單5-21 main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/selection" android:layout_width="match_parent" android:layout_height="wrap_content"/> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" android:drawSelectorOnTop="false" /> </LinearLayout>
2.修改ListViewDemo.java文件
在ListViewDemo.java文件中,首先需要為L(zhǎng)istView創(chuàng)建適配器,配置和連接列表,添加ListView中所顯示的內(nèi)容。
代碼清單5-22 ListViewDemo.java
public class ListViewDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,items)); selection=(TextView)findViewById(R.id.selection); } public void onListItemClick(ListView parent, View v, int position,long id) { selection.setText(items[position]); } }
繼承ListActivity后,可以通過setListAdapter()方法設(shè)置列表。這種情況下,提供了一個(gè)ArrayAdapter包裝的字符串?dāng)?shù)組。其中ArrayAdapter的第二個(gè)參數(shù)android.R.layout.simple_list_item_1控制了ListView中行的顯示,上例中android.R.layout.simple_list_item_1該值提供了標(biāo)準(zhǔn)的Android清單行:大字體、很多的填充、文本和白色。重寫onListItemClick方法以在列表上子項(xiàng)的選擇發(fā)生變化時(shí)及時(shí)更新其文本。
在默認(rèn)情況下,ListView只對(duì)列表子項(xiàng)的點(diǎn)擊事件進(jìn)行監(jiān)聽。但ListView也跟蹤用戶的選擇,或多個(gè)可能的選擇列表,但它需要一些變化。
? 在Java代碼中調(diào)用ListView的setChoiceMode()方法來設(shè)置選擇模式,可供選擇的模式有:CHOICE_MODE_SINGLE和CHOICE_MODE_MULTIPLE兩種??梢酝ㄟ^getListView()方法在ListActivity中獲取ListView。
? 在構(gòu)造ArrayAdapter時(shí),第二個(gè)參數(shù)選擇使用以下兩種參數(shù)可以使列表上子項(xiàng)單選或是復(fù)選:android.R.layout.simple_list_item_single_choice和android.R.layout. simple_list_item_multiple_choice,如圖5-18所示。

圖5-18 單選、復(fù)選模式
? 通過調(diào)用getCheckedItemPositions()方法來判斷用戶選擇的子項(xiàng)。
5.3.6 TabHost
Tab標(biāo)簽頁是界面設(shè)計(jì)時(shí)經(jīng)常使用的界面控件,可以實(shí)現(xiàn)多個(gè)分頁之間的快速切換,每個(gè)分頁可以顯示不同的內(nèi)容。
對(duì)Tab標(biāo)簽頁的使用,首先要設(shè)計(jì)所有的分頁的界面布局,在分頁設(shè)計(jì)完成后,使用代碼建立Tab標(biāo)簽頁,并給每個(gè)分頁添加標(biāo)識(shí)和標(biāo)題,最后確定每個(gè)分頁所顯示的界面布局。其中,每個(gè)分頁建立一個(gè)XML文件,用以編輯和保存分頁的界面布局,使用的方法與設(shè)計(jì)普通用戶界面一樣。
下面就通過一個(gè)例子來加深對(duì)Tab標(biāo)簽頁的理解,如圖5-19所示的效果圖。

圖5-19 Tab標(biāo)簽頁效果圖
1.建立一個(gè)“TabDemo”程序
程序包含兩個(gè)XML文件,分別為tab1.xml和tab2.xml,這兩個(gè)文件分別使用線性布局、相對(duì)布局和絕對(duì)布局示例中的main.xml的代碼,并將布局的ID分別定義為layout01和layout02。
其中,tab1.xml文件代碼如代碼清單5-23所示。
代碼清單5-23 tab1.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id = "@+id/layout01" … </LinearLayout>
tab2.xml文件代碼如代碼清單5-24所示。
代碼清單5-24 tab2.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/layout03" … </RelativeLayout>
2.修改TabDemo.java文件
在TabDemo.java文件中輸入代碼清單5-25所示的代碼,創(chuàng)建Tab標(biāo)簽頁,并建立子頁與界面布局直接的關(guān)聯(lián)關(guān)系。
代碼清單5-25 TabDemo.java
package com.example.TabDemo; import android.app.TabActivity; import android.os.Bundle; import android.widget.TabHost; import android.view.LayoutInflater; public class TabDemo extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabHost tabHost = getTabHost(); LayoutInflater.from(this).inflate(R.layout.tab1, tabHost.getTabContentView(),true); LayoutInflater.from(this).inflate(R.layout.tab2, tabHost.getTabContentView(),true); tabHost.addTab(tabHost.newTabSpec("TAB1") .setIndicator("線性布局").setContent(R.id.layout01)); tabHost.addTab(tabHost.newTabSpec("TAB2") .setIndicator("相對(duì)布局").setContent(R.id.layout02)); }
該段代碼中,第8行代碼“public class TabDemo extends TabActivity”的聲明TabDemo類繼承于TabActivity,與以往繼承Activity不同,TabActivity支持內(nèi)嵌多個(gè)Activity或View。
第12行代碼“TabHost tabHost = getTabHost();”通過getTabHost()方法獲得Tab標(biāo)簽頁的容器,用以承載可以點(diǎn)擊的Tab標(biāo)簽和分頁的界面布局。
第13行代碼“LayoutInflater.from(this).inflate(R.layout.tab1, tabHost. getTabContent View(),true);”通過LayoutInflater將tab1.xml文件中的布局轉(zhuǎn)換為Tab標(biāo)簽頁可以使用的View對(duì)象。
第14行代碼“tabHost.addTab(tabHost.newTabSpec("TAB1").setIndicator("線性布局").setContent(R.id.layout01));”使用addTab()方法添加了第1個(gè)分頁,tabHost.newTabSpec ("TAB1")表明在第12行代碼中建立的tabHost上,添加一個(gè)標(biāo)識(shí)為TAB1的Tab分頁,同時(shí)使用setIndicator()方法設(shè)定分頁顯示的標(biāo)題,使用setContent()方法設(shè)定分頁所關(guān)聯(lián)的界面布局。
問:在使用Tab標(biāo)簽頁時(shí),只能像上述例子中一樣將不同分頁的界面布局保存在不同的XML文件中嗎?
答:除了像上述中將不同分頁的界面布局保存在不同的XML文件中外,也可以將所有分頁的布局保存在同一個(gè)XML文件中。兩者有不同的利弊:
? 第一種方法有利于在Eclipse開發(fā)環(huán)境中進(jìn)行可視化設(shè)計(jì),并且不同分頁的界面布局在不同的文件中更加易于管理。
? 第二種方法則可以產(chǎn)生較少的XML文件,同時(shí)編碼時(shí)的代碼也會(huì)更加簡(jiǎn)潔。
5.4 菜單
菜單是應(yīng)用程序中非常重要的組成部分,能夠在不占用界面空間的前提下,為應(yīng)用程序提供統(tǒng)一的功能和設(shè)置界面,并為程序開發(fā)人員提供了易于使用的編程接口。Android系統(tǒng)支持3種菜單:選項(xiàng)菜單(Option Menu)、子菜單(Submenu)、快捷菜單(Context Menu)。
5.4.1 選項(xiàng)菜單
選項(xiàng)菜單是一種經(jīng)常被使用的Android系統(tǒng)菜單,可以分為圖標(biāo)菜單(Icon Menu)和擴(kuò)展菜單(Expanded Menu)兩類,可通過“菜單鍵”(Menu key)打開。
圖標(biāo)菜單能夠同時(shí)顯示文字和圖標(biāo),最多支持6個(gè)子項(xiàng),但圖標(biāo)菜單不支持單選框和復(fù)選框。
擴(kuò)展菜單在圖標(biāo)菜單子項(xiàng)多余6個(gè)時(shí)才出現(xiàn),通過點(diǎn)擊圖標(biāo)菜單最后的子項(xiàng)“More”才能打開。擴(kuò)展菜單是垂直的列表型菜單,不能夠顯示圖標(biāo),但支持單選框和復(fù)選框。

圖5-20 圖標(biāo)菜單

圖5-21 擴(kuò)展菜單
1.重寫onCreateOptionMenu()方法
在Android應(yīng)用程序中使用選項(xiàng)菜單,需重載Activity的onCreateOptionMenu()方法。初次使用選項(xiàng)菜單時(shí),會(huì)調(diào)用onCreateOptionMenu()方法,用來初始化菜單子項(xiàng)的相關(guān)內(nèi)容,因此這里需要設(shè)置菜單子項(xiàng)自身的子項(xiàng)ID和組ID、菜單子項(xiàng)顯示的文字和圖片等。代碼如代碼清單5-26所示。
代碼清單5-26重載onCreateOptionMenu()方法
final static int MENU_DOWNLOAD = Menu.FIRST; final static int MENU_UPLOAD = Menu.FIRST+1; @Override public boolean onCreateOptionsMenu(Menu menu){ menu.add(0,MENU_DOWNLOAD,0,"下載設(shè)置"); menu.add(0,MENU_UPLOAD,1,"上傳設(shè)置"); return true; }
第1行和第2行代碼將菜單子項(xiàng)ID定義成靜態(tài)常量,并使用靜態(tài)常量Menu.FIRST(整數(shù)類型,值為1)定義第一個(gè)菜單子項(xiàng),以后的菜單子項(xiàng)僅需在Menu.FIRST增加相應(yīng)的數(shù)值即可。
第4行代碼Menu對(duì)象作為一個(gè)參數(shù)被傳遞到方法內(nèi)部,因此在onCreateOptionsMenu()方法中,用戶可以使用Menu對(duì)象的add()方法添加菜單子項(xiàng)。其中add()方法的語法如下。
MenuItem android.view.Menu.add(int groupId, int itemId, int order, CharSequence title)
第1個(gè)參數(shù)groupId是組ID,用以批量的對(duì)菜單子項(xiàng)進(jìn)行處理和排序;第2個(gè)參數(shù)itemId是子項(xiàng)ID,是每一個(gè)菜單子項(xiàng)的唯一標(biāo)識(shí),通過子項(xiàng)ID使應(yīng)用程序能夠定位到用戶所選擇的菜單子項(xiàng);第3個(gè)參數(shù)order是定義菜單子項(xiàng)在選項(xiàng)菜單中的排列順序;第4個(gè)參數(shù)title是菜單子項(xiàng)所顯示的標(biāo)題。
第7行代碼是onCreateOptionsMenu()方法返回值,方法的返回值類型為布爾型:返回true將顯示方法中設(shè)置的菜單,否則不能夠顯示菜單。
做完以上步驟后,使用setIcon()方法和setShortcut()方法,添加菜單子項(xiàng)的圖標(biāo)和快捷鍵,如代碼清單5-27所示。
代碼清單5-27添加菜單子項(xiàng)的圖標(biāo)和快捷鍵
menu.add(0,MENU_DOWNLOAD,0,"下載設(shè)置") .setIcon(R.drawable.download); .setShortcut(','d');
代碼中,利用MENU_DOWNLOAD菜單設(shè)置圖標(biāo)和快捷鍵的代碼;第2行代碼中使用了新的圖像資源,用戶將需要使用的圖像文件復(fù)制到/res/drawable目錄下;setShortcut()方法第一個(gè)參數(shù)是為數(shù)字鍵盤設(shè)定的快捷鍵,第二個(gè)參數(shù)是為全鍵盤設(shè)定的快捷鍵,且不區(qū)分字母的大小寫。
2.重寫onPrepareOptionsMenu()方法
重載onPrepareOptionsMenu()方法,能夠動(dòng)態(tài)的添加、刪除菜單子項(xiàng),或修改菜單的標(biāo)題、圖標(biāo)和可見性等內(nèi)容。onPrepareOptionsMenu()方法的返回值的含義與onCreateOptions Menu()方法相同:返回true則顯示菜單,返回false則不顯示菜單。
代碼清單5-28所示的代碼是在用戶每次打開選項(xiàng)菜單時(shí),在菜單子項(xiàng)中顯示用戶打開該子項(xiàng)的次數(shù)。
代碼清單5-28菜單子項(xiàng)中顯示用戶打開該子項(xiàng)的次數(shù)
static int MenuUploadCounter = 0; @Override public boolean onPrepareOptionsMenu(Menu menu){ MenuItem uploadItem = menu.findItem(MENU_UPLOAD); uploadItem.setTitle("上傳設(shè)置:" +String.valueOf(MenuUploadCounter)); return true; }
第1行代碼設(shè)置一個(gè)菜單子項(xiàng)的計(jì)數(shù)器,用來統(tǒng)計(jì)用戶打開“上傳設(shè)置”子項(xiàng)的次數(shù);第4行代碼是通過將菜單子項(xiàng)的ID傳遞給menu.findItem()方法,獲取到菜單子項(xiàng)的對(duì)象;第5行代碼是通過MenuItem的setTitle()方法修改菜單標(biāo)題。
問:onCreateOptionMenu()方法和onPrepareOptionsMenu()方法有什么區(qū)別?
答:onCreateOptionMenu()方法在Menu顯示之前只調(diào)用一次;而onPrepareOptionsMenu()方法在每次顯示Menu之前都會(huì)調(diào)用,一般用它執(zhí)行Menu的更新操作。
3.onOptionsItemSelected ()方法
onOptionsItemSelected ()方法能夠處理菜單選擇事件,且該方法在每次單擊菜單子項(xiàng)時(shí)都會(huì)被調(diào)用。
下面的代碼說明了如何通過菜單子項(xiàng)的ID執(zhí)行不同的操作。
代碼清單5-29 onOptionsItemSelected()
@Override public boolean onOptionsItemSelected(MenuItem item){ switch(item.getItemId()){ case MENU_DOWNLOAD: MenuDownlaodCounter++; return true; case MENU_UPLOAD: MenuUploadCounter++; return true; } return false; }
onOptionsItemSelected ()的返回值表示是否對(duì)菜單的選擇事件進(jìn)行處理,如果已經(jīng)處理過則返回true,否則返回false;第3行的MenuItem.getItemId()方法可以獲取到被選擇菜單子項(xiàng)的ID。
程序運(yùn)行后,通過單擊“菜單鍵”可以調(diào)出程序設(shè)計(jì)的兩個(gè)菜單子項(xiàng),如圖5-22所示。

圖5-22 運(yùn)行效果圖
5.4.2 子菜單
子菜單是能夠顯示更加詳細(xì)信息的菜單子項(xiàng),如圖5-23所示。其中,菜單子項(xiàng)使用了浮動(dòng)窗體的顯示形式,能夠更好地適應(yīng)小屏幕的顯示方式。

圖5-23 菜單子項(xiàng)
Android系統(tǒng)的子菜單使用非常靈活,可以在選項(xiàng)菜單或快捷菜單中使用子菜單,有利于將相同或相似的菜單子項(xiàng)組織在一起,便于顯示和分類。但是,子菜單不支持嵌套。
子菜單的添加使用addSubMenu()方法實(shí)現(xiàn),代碼如代碼清單5-30所示。
代碼清單5-30 onOptionsItemSelected()
SubMenu uploadMenu = (SubMenu) menu.addSubMenu (0,MENU_UPLOAD,1,"上傳設(shè)置").setIcon(R.drawable.upload); uploadMenu.setHeaderIcon(R.drawable.upload); uploadMenu.setHeaderTitle("上傳參數(shù)設(shè)置"); uploadMenu.add(0,SUB_MENU_UPLOAD_A,0,"上傳參數(shù)A"); uploadMenu.add(0,SUB_MENU_UPLOAD_B,0,"上傳參數(shù)B");
第1行代碼在onCreateOptionsMenu()方法傳遞的menu對(duì)象上調(diào)用addSubMenu()方法,在選項(xiàng)菜單中添加一個(gè)菜單子項(xiàng),用戶單擊后可以打開子菜單;addSubMenu()方法與選項(xiàng)菜單中使用過的add()方法支持相同的參數(shù),同樣可以指定菜單子項(xiàng)的ID、組ID和標(biāo)題等參數(shù),并且能夠通過setIcon()方法顯示菜單的圖標(biāo)。
第2行代碼使用setHeaderIcon ()方法,定義子菜單的圖標(biāo)。
第3行定義子菜單的標(biāo)題,若不規(guī)定子菜單的標(biāo)題,子菜單將顯示父菜單子項(xiàng)標(biāo)題,即第1行代碼中“上傳設(shè)置”。
第4行和第5行在子菜單中添加了兩個(gè)菜單子項(xiàng),菜單子項(xiàng)的更新方法和選擇事件處理方法,仍然使用onPrepareOptionsMenu()方法和onOptionsItemSelected ()方法。
以上一小節(jié)的代碼為基礎(chǔ),將“上傳設(shè)置”改為子菜單,并在子菜單中添加“上傳參數(shù)A”和“上傳參數(shù)B”兩個(gè)菜單子項(xiàng)。運(yùn)行結(jié)果如圖5-24所示。

圖5-24 運(yùn)行效果圖
5.4.3 上下文菜單(Context Menu)
快捷菜單同樣采用了浮動(dòng)窗體的顯示方式,與子菜單的實(shí)現(xiàn)方式相同,但兩種菜單的啟動(dòng)方式卻截然不同。
? 啟動(dòng)方式:快捷菜單類似于普通桌面程序中的“右鍵菜單”,當(dāng)用戶點(diǎn)擊界面元素超過2秒后,將啟動(dòng)注冊(cè)到該界面元素的快捷菜單。
? 使用方法:與使用選項(xiàng)菜單的方法非常相似,需要重載onCreateContextMenu()方法和onContextItemSelected()方法。
1.onCreateContextMenu()方法
onCreateContextMenu()方法主要用來添加快捷菜單所顯示的標(biāo)題、圖標(biāo)和菜單子項(xiàng)等內(nèi)容,選項(xiàng)菜單中的onCreateOptionsMenu()方法僅在選項(xiàng)菜單第一次啟動(dòng)時(shí)被調(diào)用一次,而快捷菜單的onCreateContextMenu()方法每次啟動(dòng)時(shí)都會(huì)被調(diào)用一次。
代碼清單5-31 onCreateContextMenu ()
final static int CONTEXT_MENU_1 = Menu.FIRST; final static int CONTEXT_MENU_2 = Menu.FIRST+1; final static int CONTEXT_MENU_3 = Menu.FIRST+2; @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){ menu.setHeaderTitle("快捷菜單標(biāo)題"); menu.add(0, CONTEXT_MENU_1, 0,"菜單子項(xiàng)1"); menu.add(0, CONTEXT_MENU_2, 1,"菜單子項(xiàng)2"); menu.add(0, CONTEXT_MENU_3, 2,"菜單子項(xiàng)3"); }
ContextMenu類支持add()方法(代碼第7行)和addSubMenu()方法,可以在快捷菜單中添加菜單子項(xiàng)和子菜單。
第5行代碼的onCreateContextMenu()方法中的參數(shù):第1個(gè)參數(shù)menu是需要顯示的快捷菜單;第2個(gè)參數(shù)v是用戶選擇的界面元素;第3個(gè)參數(shù)menuInfo是所選擇界面元素的額外信息。
2.onContextItemSelected ()方法
菜單選擇事件的處理需要重載onContextItemSelected()方法,該方法在用戶選擇快捷菜單中的菜單子項(xiàng)后被調(diào)用,與onOptionsItemSelected ()方法的使用方法基本相同。
代碼清單5-32 onContextItemSelected()
@Override public oolean onContextItemSelected(MenuItem item){ switch(item.getItemId()){ case CONTEXT_MENU_1: LabelView.setText("菜單子項(xiàng)1"); return true; case CONTEXT_MENU_2: LabelView.setText("菜單子項(xiàng)2"); return true; case CONTEXT_MENU_3: LabelView.setText("菜單子項(xiàng)3"); return true; } return false; 3.registerForContextMenu()方法 }
使用registerForContextMenu()方法,將快捷菜單注冊(cè)到界面控件上(代碼清單5-33中第7行)。這樣,用戶在長(zhǎng)時(shí)間點(diǎn)擊該界面控件時(shí),便會(huì)啟動(dòng)快捷菜單。
為了能夠在界面上直接顯示用戶所選擇快捷菜單的菜單子項(xiàng),在代碼中引用了界面元素TextView(代碼清單5-33中第6行),通過更改TextView的顯示內(nèi)容(代碼清單5-32中第5、8和11行),顯示用戶所選擇的菜單子項(xiàng)。
代碼清單5-33 registerForContextMenu ()
TextView LabelView = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LabelView = (TextView)findViewById(R.id.label); registerForContextMenu(LabelView); }
4.main.xml
代碼清單5-34 main.xml
<TextView android:id="@+id/label" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello" />
上述代碼為/src/layout/main.xml文件的部分內(nèi)容,第1行聲明了TextView的ID為label,在代碼清單5-33的第6行中,通過R.id.label將ID傳遞給findViewById()方法,這樣用戶便能夠引用該界面元素,并能夠修改該界面元素的顯示內(nèi)容。
需要注意的一點(diǎn),代碼清單5-34的第2行,將android:layout_width設(shè)置為match_parent,這樣TextView將填充滿父節(jié)點(diǎn)的所有剩余屏幕空間,用戶點(diǎn)擊屏幕TextView下方任何位置都可以啟動(dòng)快捷菜單。如果將android:layout_width設(shè)置為wrap_content,則用戶必須準(zhǔn)確單擊TextView才能啟動(dòng)快捷菜單。
圖5-25為快捷菜單的運(yùn)行效果圖。

圖5-25 運(yùn)行效果圖
問:菜單可不可以像界面布局一樣在XML文件中進(jìn)行定義?
答:菜單可以像界面布局一樣在XML文件中進(jìn)行定義。使用XML文件定義界面菜單,將代碼與界面設(shè)計(jì)分類,有助于簡(jiǎn)化代碼的復(fù)雜程度,并且更有利于界面的可視化。
下面將快捷菜單的示例程序MyContextMenu改用XML實(shí)現(xiàn),新程序的工程名稱為MyXLMContoxtMenu。
首先需要?jiǎng)?chuàng)建保存菜單內(nèi)容的XML文件:在/src目錄下建立子目錄menu,并在menu下建立context_menu.xml文件,代碼如代碼清單5-35所示。
代碼清單5-35 context_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/contextMenu1" android:title="菜單子項(xiàng)1"/> <item android:id="@+id/contextMenu2" android:title="菜單子項(xiàng)2"/> <item android:id="@+id/contextMenu3" android:title="菜單子項(xiàng)3"/> </menu>
在描述菜單的XML文件中,必須以<menu>標(biāo)簽(代碼第1行)作為根節(jié)點(diǎn),<item>標(biāo)簽(代碼第2行)用來描述菜單中的子項(xiàng),<item>標(biāo)簽可以通過嵌套實(shí)現(xiàn)子菜單的功能。
XML菜單的顯示結(jié)果如圖5-26所示。

圖5-26 XML菜單的顯示結(jié)果
在XML文件中定義菜單后,在onCreateContextMenu()方法中調(diào)用inflater.inflate()方法,將XML資源文件傳遞給菜單對(duì)象,代碼如代碼清單5-36所示。
代碼清單5-36 onCreateContextMenu()
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){ MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.context_menu, menu); }
第4行代碼中的getMenuInflater()為當(dāng)前的Activity返回MenuInflater;第5行代碼將XML資源文件R.menu.context_menu,傳遞給menu這個(gè)快捷菜單對(duì)象。
5.5 界面事件
在Android系統(tǒng)中,存在多種界面事件,如點(diǎn)擊事件、觸摸事件、焦點(diǎn)事件和菜單事件等,在這些界面事件發(fā)生時(shí),Android界面框架調(diào)用界面控件的事件處理方法對(duì)事件進(jìn)行處理。
Android系統(tǒng)界面事件的傳遞和處理遵循以下規(guī)則。
? 如果界面控件設(shè)置了事件監(jiān)聽器,則事件將先傳遞給事件監(jiān)聽器。
? 如果界面控件沒有設(shè)置事件監(jiān)聽器,界面事件則會(huì)直接傳遞給界面控件的其他事件處理方法。
? 即使界面控件設(shè)置了事件監(jiān)聽器,界面事件也可以再次傳遞給其他事件處理方法。
? 是否繼續(xù)傳遞事件給其他處理方法是由事件監(jiān)聽器處理方法的返回值決定的。
? 如果監(jiān)聽器處理方法的返回值為true,表示該事件已經(jīng)完成處理過程,不需要其他處理方法參與處理過程,這樣事件就不會(huì)再繼續(xù)進(jìn)行傳遞。
? 如果監(jiān)聽器處理方法的返回值為false,則表示該事件沒有完成處理過程,或需要其他處理方法捕獲到該事件,事件會(huì)被傳遞給其他的事件處理方法。
在MVC模型中,控制器根據(jù)界面事件(UI Event)類型不同,將事件傳遞給界面控件不同的事件處理方法。
? 按鍵事件(KeyEvent)將傳遞給onKey()方法進(jìn)行處理。
? 觸摸事件(TouchEvent)將傳遞給onTouch()方法進(jìn)行處理。
5.5.1 按鍵事件
下面以EditText控件中的按鍵事件為例,說明Android系統(tǒng)界面事件傳遞和處理過程。
假設(shè)EditText控件已經(jīng)設(shè)置了按鍵事件監(jiān)聽器,當(dāng)用戶按下鍵盤上的某個(gè)按鍵時(shí),控制器將產(chǎn)生KeyEvent按鍵事件。Android系統(tǒng)會(huì)首先判斷EditText控件是否設(shè)置了按鍵事件監(jiān)聽器,因?yàn)镋ditText控件已經(jīng)設(shè)置按鍵事件監(jiān)聽器OnKeyListener,所以按鍵事件先傳遞到監(jiān)聽器的事件處理方法onKey()中,事件能夠繼續(xù)傳遞給EditText控件的其他事件處理方法,完全根據(jù)onKey()方法的返回值來確定:如果onKey()方法返回false,事件將繼續(xù)傳遞,這樣EditText控件就可以捕獲到該事件,將按鍵的內(nèi)容顯示在EditText控件中;如果onKey()方法返回true,將阻止按鍵事件的繼續(xù)傳遞,這樣EditText控件就不能夠捕獲到按鍵事件,也就不能夠?qū)存I內(nèi)容顯示在EditText控件中。
Android界面框架支持對(duì)按鍵事件的監(jiān)聽,并能夠?qū)存I事件的詳細(xì)信息傳遞給處理方法。為了處理控件的按鍵事件,先需要設(shè)置按鍵事件的監(jiān)聽器,并重載onKey()方法,示例代碼如代碼清單5-37所示。
代碼清單5-37設(shè)置按鍵事件的監(jiān)聽器,并重載onKey()方法
entryText.setOnKeyListener(new OnKeyListener(){ @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { //過程代碼…… return true/false; }
第1行代碼是設(shè)置控件的按鍵事件監(jiān)聽器。
第3行代碼的onKey ()方法中的參數(shù):第1個(gè)參數(shù)View表示產(chǎn)生按鍵事件的界面控件;第2個(gè)參數(shù)keyCode表示按鍵代碼;第3個(gè)參數(shù)KeyEvent則包含了事件的詳細(xì)信息,如按鍵的重復(fù)次數(shù)、硬件編碼和按鍵標(biāo)志等。
第5行代碼是onKey()方法的返回值:返回true,阻止事件傳遞;返回false,允許繼續(xù)傳遞按鍵事件。
KeyEventDemo是一個(gè)說明如何處理按鍵事件的示例。
KeyEventDemo用戶界面如圖5-27所示。

圖5-27 KeyEventDemo用戶界面
從圖5-27中可以看出,最上方的EditText控件是輸入字符的區(qū)域,中間的CheckBox控件用來控制onKey()方法的返回值,最下方的TextView控件用來顯示按鍵事件的詳細(xì)信息,包括按鍵動(dòng)作、按鍵代碼、按鍵字符、UNICODE編碼、重復(fù)次數(shù)、功能鍵狀態(tài)、硬件編碼和按鍵標(biāo)志。
界面的XML文件的代碼如代碼清單5-38所示
代碼清單5-38界面XML文件
<EditText android:id="@+id/entry" android:layout_width="match_parent" android:layout_height="wrap_content"> </EditText> <CheckBox android:id="@+id/block" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="返回true,阻止將按鍵事件傳遞給界面元素" > </CheckBox> <TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按鍵事件信息" > </TextView>
在EditText中,當(dāng)任何一個(gè)鍵按下或抬起時(shí),都會(huì)引發(fā)按鍵事件。為了能夠使EditText處理按鍵事件,需要使用setOnKeyListener ()方法在代碼中設(shè)置按鍵事件監(jiān)聽器,并在onKey()方法中添加按鍵事件的處理過程,代碼如代碼清單5-39所示。
代碼清單5-39 setOnKeyListener()
entryText.setOnKeyListener(new OnKeyListener(){ @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { int metaState = keyEvent.getMetaState(); int unicodeChar = keyEvent.getUnicodeChar(); String msg = ""; msg +="按鍵動(dòng)作:" + String.valueOf(keyEvent.getAction())+"\n"; msg +="按鍵代碼:" + String.valueOf(keyCode)+"\n"; msg +="按鍵字符:" + (char)unicodeChar+"\n"; msg +="UNICODE:" + String.valueOf(unicodeChar)+"\n"; msg +="重復(fù)次數(shù):" + String.valueOf(keyEvent.getRepeatCount())+"\n"; msg +="功能鍵狀態(tài):" + String.valueOf(metaState)+"\n"; msg +="硬件編碼:" + String.valueOf(keyEvent.getScanCode())+"\n"; msg +="按鍵標(biāo)志:" + String.valueOf(keyEvent.getFlags())+"\n"; labelView.setText(msg); if (checkBox.isChecked()) return true; else return false; }
在上述代碼中,第4行代碼用來獲取功能鍵狀態(tài)。功能鍵包括左Alt鍵、右Alt鍵和Shift鍵,當(dāng)這3個(gè)功能鍵被按下時(shí),功能鍵代碼metaState值分別為18、34和65;但沒有功能鍵被按下時(shí),功能鍵代碼metaState值分別為0。
第5行代碼獲取了按鍵的Unicode值,而在第9行中,將Unicode轉(zhuǎn)換為了字符,顯示在TextView中。
第7行代碼獲取了按鍵動(dòng)作,0表示按下按鍵,1表示抬起按鍵。第7行代碼獲取按鍵的重復(fù)次數(shù),但當(dāng)按鍵被長(zhǎng)時(shí)間按下時(shí),則會(huì)產(chǎn)生這個(gè)屬性值。
第13行代碼獲取了按鍵的硬件編碼,各硬件設(shè)備的按鍵硬件編碼都不相同,因此該值一般用于調(diào)試。
第14行獲取了按鍵事件的標(biāo)志符。
5.5.2 觸摸事件
Android界面框架支持對(duì)觸摸事件的監(jiān)聽,并能夠?qū)⒂|摸事件的詳細(xì)信息傳遞給處理方法,不過需要設(shè)置觸摸事件的監(jiān)聽器,并重載onTouch ()方法。
設(shè)置觸摸事件的監(jiān)聽器,并重載onTouch ()方法的代碼如代碼清單5-40所示。
代碼清單5-40設(shè)置觸摸事件的監(jiān)聽器,并重載onTouch ()方法
touchView.setOnTouchListener(new View.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { //過程代碼… return true/false; }
在上述代碼中,第1行代碼是設(shè)置控件的觸摸事件監(jiān)聽器;在第3行的onTouch()方法中,第1個(gè)參數(shù)View表示產(chǎn)生觸摸事件的界面控件;第2個(gè)參數(shù)MontionEvent表示觸摸事件的詳細(xì)信息,如產(chǎn)生時(shí)間、坐標(biāo)和觸點(diǎn)壓力等;第5行是onTouch()方法的返回值。
TouchEventDemo是一個(gè)說明如何處理觸摸事件的示例。
TouchEventDemo用戶界面如圖5-28所示。

圖5-28 TouchEventDemo用戶界面
由圖5-28可以看出,上半部分的淺藍(lán)色區(qū)域是可以接受觸摸事件的區(qū)域,用戶可以在Android模擬器中使用鼠標(biāo)點(diǎn)擊屏幕用以模擬觸摸手機(jī)屏幕;下方黑色區(qū)域是顯示區(qū)域,用來顯示觸摸事件類型、相對(duì)坐標(biāo)、絕對(duì)坐標(biāo)、觸點(diǎn)壓力、觸點(diǎn)尺寸和歷史數(shù)據(jù)量等信息。
在用戶界面中使用了線性布局,并加入了3個(gè)TextView控件:第1個(gè)TextView(ID為touch_area)用來標(biāo)識(shí)觸摸事件的測(cè)試區(qū)域;第2個(gè)TextView(ID為history_label)用來顯示觸摸事件的歷史數(shù)據(jù)量;第3個(gè)TextView(ID為event_label)用來顯示觸摸事件的詳細(xì)信息,包括類型、相對(duì)坐標(biāo)、絕對(duì)坐標(biāo)、觸點(diǎn)壓力和觸點(diǎn)尺寸。
XML文件的代碼如代碼清單5-41所示。
代碼清單5-41 XML文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/touch_area" android:layout_width="match_parent" android:layout_height="300dip" android:background="#80A0FF " android:textColor="#FFFFFF" android:text="觸摸事件測(cè)試區(qū)域"> </TextView> <TextView android:id="@+id/history_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="歷史數(shù)據(jù)量:" > </TextView> <TextView android:id="@+id/event_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="觸摸事件:" > </TextView> </LinearLayout>
在上述代碼中,第9行代碼定義了TextView的背景顏色,#80A0FF是顏色代碼;第10行代碼定義了TextView的字體顏色。
在代碼中為了能夠引用XML文件中聲明的界面元素,使用了代碼清單5-42所示的代碼。
代碼清單5-42在代碼中引用XML文件中聲明的界面元素
TextView labelView = null; labelView = (TextView)findViewById(R.id.event_label); TextView touchView = (TextView)findViewById(R.id.touch_area); final TextView historyView = (TextView)findViewById(R.id.history_label);
當(dāng)手指接觸到觸摸屏、在觸摸屏上移動(dòng)或離開觸摸屏?xí)r,分別會(huì)引發(fā)ACTION_DOWN、ACTION_UP和ACTION_MOVE觸摸事件,而無論是哪種觸摸事件,都會(huì)調(diào)用onTouch()方法進(jìn)行處理。事件類型包含在onTouch()方法的MotionEvent參數(shù)中,可以通過getAction()方法獲取到觸摸事件的類型,然后根據(jù)觸摸事件的不同類型進(jìn)行不同的處理。為了能夠使屏幕最上方的TextView處理觸摸事件,需要使用setOnTouchListener()方法在代碼中設(shè)置觸摸事件監(jiān)聽器,并在onTouch()方法添加觸摸事件的處理過程。代碼如代碼清單5-43所示。
代碼清單5-43 onTouch()
touchView.setOnTouchListener(new View.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case (MotionEvent.ACTION_DOWN): Display("ACTION_DOWN",event); break; case (MotionEvent.ACTION_UP): int historySize = ProcessHistory(event); historyView.setText("歷史數(shù)據(jù)量:"+historySize); Display("ACTION_UP",event); break; case (MotionEvent.ACTION_MOVE): Display("ACTION_MOVE",event); break; } return true; } });
第7行代碼的Display()是一個(gè)自定義方法,主要用來顯示觸摸事件的詳細(xì)信息,方法的代碼和含義將在后面進(jìn)行介紹;第10行代碼的ProcessHistory()也是一個(gè)自定義方法,用來處理觸摸事件的歷史數(shù)據(jù);第11行代碼是使用TextView顯示歷史數(shù)據(jù)的數(shù)量。
MotionEvent參數(shù)中不僅有觸摸事件的類型信息,還有觸點(diǎn)的坐標(biāo)信息,獲取方式是使用getX()和getY()方法,這兩個(gè)方法獲取到的是觸點(diǎn)相對(duì)于父界面元素的坐標(biāo)信息。如果需要獲取絕對(duì)坐標(biāo)信息,則可使用getRawX()和getRawY()方法。
觸點(diǎn)壓力是一個(gè)介于0和1之間的浮點(diǎn)數(shù),用來表示用戶對(duì)觸摸屏施加壓力的大小,接近0 表示壓力較小,接近1 表示壓力較大,獲取觸摸事件觸點(diǎn)壓力的方式是調(diào)用getPressure()方法。
觸點(diǎn)尺寸指用戶接觸觸摸屏的接觸點(diǎn)大小,也是一個(gè)介于0和1之間的浮點(diǎn)數(shù),接近0表示尺寸較小,接近1表示尺寸較大,可以使用getSize()方法獲取。
Display()將MotionEvent參數(shù)中的事件信息提取出來,并顯示在用戶界面上。代碼如代碼清單5-44所示。
代碼清單5-44 Display()
private void Display(String eventType, MotionEvent event){ int x = (int)event.getX(); int y = (int)event.getY(); float pressure = event.getPressure(); float size = event.getSize(); int RawX = (int)event.getRawX(); int RawY = (int)event.getRawY(); String msg = ""; msg += "事件類型:" + eventType + "\n"; msg += "相對(duì)坐標(biāo):"+String.valueOf(x)+","+String.valueOf(y)+"\n"; msg += "絕對(duì)坐標(biāo):"+String.valueOf(RawX)+","+String.valueOf(RawY)+"\n"; msg += "觸點(diǎn)壓力:"+String.valueOf(pressure)+", "; msg += "觸點(diǎn)尺寸:"+String.valueOf(size)+"\n"; labelView.setText(msg); }
一般情況下,如果用戶將手指放在觸摸屏上,但不移動(dòng),然后抬起手指,應(yīng)先后產(chǎn)生ACTION_DOWN和ACTION_UP兩個(gè)觸摸事件。但如果用戶在屏幕上移動(dòng)手指,然后再抬起手指,則會(huì)產(chǎn)生這樣的事件序列:ACTION_DOWN → ACTION_MOVE →ACTION_MOVE → ACTION_MOVE → …→ ACTION_UP。
在手機(jī)上運(yùn)行的應(yīng)用程序,效率是非常重要的。如果Android界面框架不能產(chǎn)生足夠多的觸摸事件,則應(yīng)用程序就不能夠很精確地描繪觸摸屏上的觸摸軌跡。如果Android界面框架產(chǎn)生了過多的觸摸事件,雖然能夠滿足精度的要求,但也降低了應(yīng)用程序效率。
針對(duì)以上問題Android界面框架使用了“打包”的解決方法。在觸點(diǎn)移動(dòng)速度較快時(shí)會(huì)產(chǎn)生大量的數(shù)據(jù),每經(jīng)過一定的時(shí)間間隔便會(huì)產(chǎn)生一個(gè)ACTION_MOVE事件,在這個(gè)事件中,除了有當(dāng)前觸點(diǎn)的相關(guān)信息外,還包含這段時(shí)間間隔內(nèi)觸點(diǎn)軌跡的歷史數(shù)據(jù)信息,這樣既能夠保持精度,又不至于產(chǎn)生過多的觸摸事件。
通常情況下,在ACTION_MOVE的事件處理方法中,都先處理歷史數(shù)據(jù),然后再處理當(dāng)前數(shù)據(jù),代碼如代碼清單5-45所示。
代碼清單5-45 ProcessHistory(MotionEvent event)
private int ProcessHistory(MotionEvent event) { int historySize = event.getHistorySize(); for (int i = 0; i < historySize; i++) { long time = event.getHistoricalEventTime(i); float pressure = event.getHistoricalPressure(i); float x = event.getHistoricalX(i); float y = event.getHistoricalY(i); float size = event.getHistoricalSize(i); // 處理過程… } return historySize; }
在上述代碼中,第3行代碼獲取了歷史數(shù)據(jù)的數(shù)量;然后在第4行至12行中循環(huán)處理這些歷史數(shù)據(jù);第5行代碼獲取了歷史事件的發(fā)生時(shí)間;第6行代碼獲取歷史事件的觸點(diǎn)壓力;第7行和第8行代碼獲取歷史事件的相對(duì)坐標(biāo);第9行獲取歷史事件的觸點(diǎn)尺寸;在第14行返回歷史數(shù)據(jù)的數(shù)量,主要是用于界面顯示。
問:Android模擬器支持觸點(diǎn)壓力和觸點(diǎn)尺寸的模擬嗎?
答:Android模擬器并不支持觸點(diǎn)壓力和觸點(diǎn)尺寸的模擬,所有觸點(diǎn)壓力恒為1.0,觸點(diǎn)尺寸恒為0.0。同時(shí),Android模擬器上也無法產(chǎn)生歷史數(shù)據(jù),因此歷史數(shù)據(jù)量一直顯示為0。
5.6 自定義樣式和主題
Android也可以像HTML/CSS中的style一樣,使用自定義的style樣式。Android一般通過value文件夾下面新建一個(gè)styles.xml文件來設(shè)置自定義樣式。這里開發(fā)者可以設(shè)置高度、填充字體顏色、字體大小、背景顏色等描述一個(gè)View或者一個(gè)窗口的顯示屬性。這就像Web開發(fā)中的CSS樣式表,使我們的樣式獨(dú)立于內(nèi)容進(jìn)行設(shè)計(jì)開發(fā)。
主題和樣式都是通過在xml文件中預(yù)定義一系列屬性值,通過這些屬性值來形成統(tǒng)一的顯示風(fēng)格。不同的是,樣式只能應(yīng)用于某種類型的View;而主題剛好相反,不能應(yīng)用于特定的View,而只能作用于一個(gè)或多個(gè)Activity,或是整個(gè)應(yīng)用。
下面通過代碼學(xué)習(xí)一下如何自定義樣式與主題,并在程序中應(yīng)用。
首先是自定義樣式和主題。在項(xiàng)目的res/values/目錄下添加styles.xml。如代碼清單5-46所示。
代碼清單5-46 styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- 定義my_style_1,沒有指定parent,用系統(tǒng)默認(rèn)的 --> <style name="my_style_1"> <!-- 定義與指定View相關(guān)的若干屬性 --> <item name="android:hint">load from style 1</item> </style> <!-- 定義my_style_2,用自定義的my_style_1作為parent --> <style name="my_style_2" parent="@style/my_style_1"> <!-- 定義與指定View相關(guān)的若干屬性 --> <item name="android:textSize">30sp</item> <item name="android:textColor">#FFFF0000</item> <item name="android:hint">load from style 2</item> </style> <!-- 定義my_style_3,用android的EditText作為parent --> <style name="my_style_3" parent="@android:style/Widget.EditText"> <!-- 定義與指定View相關(guān)的若干屬性 --> <item name="android:hint">"load from style 3"</item> <item name="android:textStyle">bold|italic</item> <item name="android:typeface">monospace</item> <item name="android:background">@drawable/mybackground</item> </style> <!-- 定義MyTheme,用android的Theme作為parent --> <style name="MyTheme" parent="@android:style/Theme"> <item name="android:textSize">20sp</item> <item name="android:textColor">#FF0000FF</item> <item name="android:hint">"load from style 3"</item> <item name="android:textStyle">bold|italic</item> <item name="android:typeface">monospace</item> <item name="android:background">@drawable/gallery_selected_pressed</item> <item name="myStyle">@style/my_style_3</item> </style> </resources>
由上述代碼可以看出,主題和樣式都可以通過在<style>下添加多個(gè)<item>來完成其定義。下面來介紹一下<style>的屬性,如表5-12所示。
表5-12 <style>的屬性

在<item>中定義需要改變的屬性值,例如,可以定義顏色、高度等。Android中能使用的屬性可以在<sdk>/docs/reference/android/R.styleable.html中查到;也可以用自己定義的屬性值。
下面講解如何在程序中使用樣式:一方面可以在layout的布局文件中指定自定義的樣式;另一方面可以在java代碼中指定。具體代碼如代碼清單5-47、5-48所示。
代碼清單5-47在布局文件中指定自定義樣式
<EditText android:id="@+id/EditText03" style="@style/my_style_3" android:layout_width="fill_parent" android:layout_height="wrap_content"> </EditText>
代碼清單5-48在java代碼中指定自定義樣式
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LinearLayout ll = (LinearLayout)findViewById(R.id.main); EditText et = new EditText(this, null, R.attr.myStyle);//指定樣式 ll.addView(et); }
如何在程序中使用自定義主題呢?這與使用自定義樣式相似,都可以通過兩個(gè)方法指定,不同的是主題除了可以在java代碼中指定以外,還可以在AndroidManifest中被指定。具體代碼如代碼清單5-49、5-50、5-51所示。
代碼清單5-49在AndroidManifest.xml中指定自定義主題——應(yīng)用于整個(gè)程序
<application android:theme="@style/MyTheme">
代碼清單5-50在AndroidManifest.xml中指定自定義主題——應(yīng)用于Activity
<activity android:theme="@style/MyTheme">
代碼清單5-51在java代碼中指定自定義主題
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(R.style.MyTheme);//設(shè)置主題 setContentView(R.layout.main); }
注意:
setTheme必須在setContentView(),addContentView()或inflate()等實(shí)例化View的函數(shù)之前調(diào)用。
5.7 9Patch
9Patch是一個(gè)對(duì)png圖片做處理的工具。經(jīng)9Patch處理過的圖片以*.9.png結(jié)尾,和普通圖片相比,四周多了一個(gè)邊框,如圖5-29所示。

圖5-29 9Patch處理過的圖片*.9.png
“*.9.png”是一種被Android os所支持的特殊圖片格式,是經(jīng)過9Patch特殊處理過的圖片。用它可以實(shí)現(xiàn)部分拉伸而不出現(xiàn)拉伸以后圖片失真等不良現(xiàn)象。為將普通的PNG圖片編輯轉(zhuǎn)化為9Patch圖片,可以使用Android SDK/tools目錄下提供的draw9patch.bat編輯器。draw9patch.bat編輯器界面如圖5-30所示。

圖5-30 draw9Patch.bat編輯器界面
在draw9patch.bat編輯器中,可以對(duì)導(dǎo)入的png圖片進(jìn)行以下操作來達(dá)到想要的圖片目標(biāo)。
? Zoom:縮放左邊編輯區(qū)域的大小。
? Patch scale:縮放右邊預(yù)覽區(qū)域的大小。
? Show lock:當(dāng)鼠標(biāo)在圖片區(qū)域時(shí),顯示不可編輯區(qū)域。
? Show patches:在編輯區(qū)域顯示圖片拉伸的區(qū)域。
? Show content:在預(yù)覽區(qū)域顯示圖片的內(nèi)容區(qū)域。
? Show bad patches:在拉伸區(qū)域周圍用顯示可能會(huì)對(duì)拉伸后的圖片產(chǎn)生變形的區(qū)域;根據(jù)圖片的顏色值來區(qū)分是否為bad patch。
5.8 本章小結(jié)
本章主要對(duì)Android程序界面開發(fā)的學(xué)習(xí),包括用戶界面基礎(chǔ),界面布局特點(diǎn)及使用方法,用戶界面控件的使用,選項(xiàng)菜單、子菜單和快捷菜單的使用方法,界面事件的處理方法等。
關(guān)鍵知識(shí)點(diǎn)測(cè)評(píng)
1.以下有關(guān)Android用戶界面的說法,不正確的一個(gè)是(?。?。
A.Android用戶界面框架按照“先進(jìn)后出”的規(guī)則從隊(duì)列中獲取事件
B.Android系統(tǒng)的資源文件獨(dú)立保存在資源文件夾中
C.Android系統(tǒng)允許不明確定義界面元素的位置和尺寸
D.Android系統(tǒng)使用XML文件描述用戶界面
2.以下有關(guān)Android組件的敘述,正確的一個(gè)是(?。?/p>
A.在程序運(yùn)行時(shí)動(dòng)態(tài)添加或修改界面布局使得在后期修改用戶界面時(shí),無須更改程序的源代碼
B.絕對(duì)布局能夠最大程度保證在各種屏幕類型的手機(jī)上正確顯示界面布局
C.框架布局中,如果有多個(gè)子元素,后放置的子元素將遮擋先放置的子元素
D.在程序運(yùn)行時(shí)動(dòng)態(tài)添加或修改界面布局可以使得程序的表現(xiàn)層和控制層分離
3.以下有關(guān)Android系統(tǒng)控件的描述,不正確的是(?。?。
A.使用ListView時(shí),如果顯示內(nèi)容過多,則會(huì)出現(xiàn)垂直滾動(dòng)條
B.Spinner使用浮動(dòng)菜單為用戶提供選擇
C.RadioGroup是RadioButton的承載體,程序運(yùn)行時(shí)不可見,在每個(gè)RadioGroup中,用戶僅能夠選擇其中一個(gè)RadioButton
D.使用Tab標(biāo)簽頁只能將不同分頁的界面布局保存在不同的XML文件中
- Unity 2020 By Example
- 極簡(jiǎn)算法史:從數(shù)學(xué)到機(jī)器的故事
- Visual Basic程序開發(fā)(學(xué)習(xí)筆記)
- Hands-On Image Processing with Python
- Java Web及其框架技術(shù)
- 趣學(xué)Python算法100例
- Mastering OpenCV 4
- 深度強(qiáng)化學(xué)習(xí)算法與實(shí)踐:基于PyTorch的實(shí)現(xiàn)
- 程序員修煉之道:通向務(wù)實(shí)的最高境界(第2版)
- Python深度學(xué)習(xí):基于TensorFlow
- Python深度學(xué)習(xí):模型、方法與實(shí)現(xiàn)
- 人工智能算法(卷1):基礎(chǔ)算法
- Java7程序設(shè)計(jì)入門經(jīng)典
- Python+Office:輕松實(shí)現(xiàn)Python辦公自動(dòng)化
- Java Web開發(fā)基礎(chǔ)與案例教程