Chapter 5 基礎UI設計
Android與UI的關係 此章節將簡介有關Android 與UI的基本關係,主要介紹內容為如何使用Style樣式設計使用者介面,最後講解有關各種視窗事件的處理方式,讓程式設計者先有一簡單的概念。較詳細之UI將在第六章說明。
Android與UI的關係 使用者介面有兩種方式可以呈現 用來顯示資料、影像或是其他訊息的元件,都被叫做View。 與主程式混合寫在一起 寫在XML中 用來顯示資料、影像或是其他訊息的元件,都被叫做View。 View是大部分UI的父類別 UI(User Interface)是介於使用者與硬體而設計彼此之間互動溝通相關軟體,目的在使用者能夠方便有效率地去操作硬體以達成雙向之互動,完成希望藉助硬體完成的工作。使用者介面定義廣泛,包含了人機互動與圖形使用者介面,凡參與人類與機械的資訊交流的領域都存在著使用者介面。 一個系統如果沒有流暢、便利的使用者介面,那這個系統只能發揮不到一半的效能,不論是電腦或手機都需要良好的使用者介面。像是現在非常有名的HTC Sense UI,並不是使用原本Android中所提供的UI,而是根據使用者的需求所打造出來,這也是HTC成功的一個原因,這套UI廣泛使用於旗下的手機當中,目前也不斷的更新精進。由此可見,UI在手機系統當中占有非常重要的一席之地。 在Android系統中,使用者介面有兩種方式可以呈現,一種是與主程式混合寫在一起,另一種則是寫在XML中,通常我們都採用後者。用來顯示資料、影像或是其他訊息的元件,都被叫做View。View是大部分UI的父類別,例如:TextView、ListView、TableLayout...等等,ViewGroup是一種View容器。
Android與UI的關係 ViewGroup是一種View容器,本身也是一種View,但是可以包含View及其他ViewGroup元件的View,例如: LinearLayout。通常會先建構出ViewGroup容器元件,像是 LinearLayout 物件實體後,接著呼叫addview(View物件實體, LinearLayout.Params物件實體) 的方法,將View物件實體,以指定的參數 LinearLayout.Params物件實體加進來組合。 上圖說明了在Android Appliation中的UI架構,但與API中的物件導向階層架構並不相同。因此了解到整個Application架構就是以ViewGroup元件為一個大容器,可以放置View及ViewGroup。但是從物件導向的觀點來看,ViewGroup繼承自View,所以ViewGroup is-a View的觀念,只是ViewGroup有容器的特色。 ViewGroup是一種View容器,本身也是一種View,但是可以包含View及其他ViewGroup元件的View。
Style樣式設計 此章節將簡介有關Android 與UI的關係,並講述如何使用Style樣式設計使用者介面,最後講解有關各種視窗事件的處理方式。
Style樣式設計 設計使用者介面的兩種方式 Styles Themes 是一個包含一種或者多種格式化屬性的集合,可以將其套用在佈局XML的單一元素中。 Themes 是一個包含一種或者多種格式化屬性的集合,可以將其套用在應用程式中所有的活動當中或其中的某個活動。 當在設計使用者介面時,有styles和themes兩種方式可以套用各種的UI元素及多種佈景。在此章節將會講解style樣式設計方法,theme主題風格設計將會於下一章節中介紹。 Style:是一個包含一種或者多種格式化屬性的集合,我們可以將其套用在佈局XML的單一元素中。舉例來說,你可以定義一種style來規範字體大小和顏色,然後套用在特定的View元素上。 Theme:是一個包含一種或者多種格式化屬性的集合,我們可以將套用在應用程式中所有的活動當中或其中的某個活動。舉例來說,我們可以定義一個Theme,它為window frame和panel 的前景和背景定義了一組顏色,並為功能表定義字體的大小和顏色屬性,可以將這個Theme套用在應用程式的所有活動或只套用在某一個活動上。
Style樣式設計 Android系統中有提供一些預設的style 如果要產生客製化的style,首先要在res/values目錄下建立一個名為style.xml的文件
Style樣式設計 布局文件(res/values/style.xml): <?xml version="1.0" encoding="utf-8" ?> <resources> <style name="StyleText1"> <item name="android:textSize">18sp</item> <item name="android:textColor">#EC9237</item> </style> <style name="StyleText2"> <item name="android:textSize">14sp</item> <item name="android:textColor">#FF7F7C</item> <item name="android:fromAlpha">0.0</item> <item name="android:toAlpha">0.0</item> </resources> style.xml定義內容如下
Style樣式設計 布局文件(res/layout/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="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_height="wrap_content" android:text="@string/hello" /> <TextView style = "@style/StyleText1" android:text="StyleText1" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"> </TextView> <TextView style = "@style/StyleText2" android:text="StyleText2" android:id="@+id/TextView02" </LinearLayout> 接著加入兩個TextView,修改res/layout/main.xml 接著加入兩個TextView,修改res/layout/main.xml內容如下:
Style樣式設計 關於上述範例使用的一些style屬性,可參考第四章各節所提供之屬性表格。 接著將程式部署至模擬器上,實際情況如圖所示。
視窗事件處理
視窗事件處理 利用UI的事件處理(UI Events),和使用者「互動」: 在Android中有超過一種以上監聽使用者與應用程式互動的方法。 當事件發生在使用者介面上時,截取使用者與特定View互動的事件,在View類別中就提供了此類的方法。 學會產生基本的UI後,接著就要學習UI的事件處理(UI Events),才能讓UI與使用者「互動」。 在Android中有超過一種以上的監聽使用者與應用程式互動的方法,當事件發生在使用者介面上時, 方法就是捕捉使用者與特定View互動的事件,在View類別中就提供了這樣的方法。 使用多種不同的View組成屬於自己的佈局,在其中有許多針對UI事件的方法,當各種動作發生在某個物件上時,這些方法就會由Android framework所呼叫。 舉例來說,當一個View(按鈕)被點選,這時onTouchEvent()方法就會被呼叫。然而,為了監聽這個動作,必需繼承及覆寫這個方法,但為了處理這類事件, 每個View都需要被繼承,實際上來說並不實用。這就是為何View類別也包含巢狀介面的集合,使得這些事件可以更容易去定義,這些介面就被稱為event listeners。 雖然你會更常使用event listeners去監聽使用者互動,但有時候會為了客製化容器而去繼承某個View類別,也許是繼承Button類別創造出更有趣的東西。 針對這個例子而言,你將可以使用event handlers去定義預設的事件行為。
視窗事件處理 Android framework呼叫不同的view中,針對許多UI事件的方法。 例如:當一個View(按鈕)被點選,這時onTouchEvent()方法就會被呼叫。 為了監聽這個動作,必需繼承及覆寫這個方法,但為了處理這類事件,每個View都需要被繼承,實際上來說並不實用。 View類別也包含巢狀介面的集合,使得這些事件可以更容易去定義,這些介面就被稱為event listeners。 使用多種不同的View組成屬於自己的佈局,在其中有許多針對UI事件的方法,當各種動作發生在某個物件上時, 這些方法就會由Android framework所呼叫。舉例來說,當一個View(按鈕)被點選,這時onTouchEvent()方法就會被呼叫。 然而,為了監聽這個動作,必需繼承及覆寫這個方法,但為了處理這類事件,每個View都需要被繼承,實際上來說並不實用。 這就是為何View類別也包含巢狀介面的集合,使得這些事件可以更容易去定義,這些介面就被稱為event listeners。 雖然你會更常使用event listeners去監聽使用者互動,但有時候會為了客製化容器而去繼承某個View類別, 也許是繼承Button類別創造出更有趣的東西。針對這個例子而言,你將可以使用event handlers去定義預設的事件行為。
Event Listeners 一個event listener在View類別中是一個介面並包含一個callback的方法。 當使用者與View的物件互動,此listener將會被註冊,這些方法將會由Android framework所呼叫。
Event Listeners event listener有下列幾種callback方法: onClick() onLongClick() onFocusChange() onKey() onTouch() onCreateContextMenu() onClick() 屬於View.OnClickListener,當使用者觸碰到某項目(在觸碰模式下)或使用導航鍵、軌跡球並按下enter鍵或軌跡球。 onLongClick() 屬於View.OnLongClickListener,當使用者觸碰到某項目(在觸碰模式下)且按住不放或使用導航鍵、軌跡球並按下enter鍵或軌跡球(一秒左右)。 onFocusChange() 屬於View.OnFocusChangeListener,使用導航鍵或軌跡球移至項目上或移開到其他項目。 onKey() 屬於View.OnKeyListener,當使用者按下或放開一個鍵。 onTouch() 屬於View.OnTouchListener,當使用者完成一個觸碰事件的動作,包含在螢幕按下、放開或其他移動手勢。 onCreateContextMenu() 屬於View.OnCreateContextMenuListener,當一個ContextMenu被建立時。
Event Listeners 程式碼(src/ncu/bnlab/OnClickListenerExample.java): public OnClickListener mSendListener = new OnClickListener() { public void onClick(View v) /* 當按鈕被按下所需執行的動作 */ Toast.makeText( OnClickEX2.this, "Yes.", Toast.LENGTH_LONG).show(); } }; @Override public void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button = (Button)findViewById(R.id.send); /* 註冊onClick listener */ button.setOnClickListener(mSendListener); 此處針對Event Listeners撰寫一範例,改寫onClick事件。 前頁的方法個別屬於不同的介面,要定義其中的方法去處理UI事件,必需實作巢狀介面在Activity中或定義成一個匿名類別。 本範例說明了如何為一個按鈕註冊一個OnClickListener。
Event Listeners 針對按鈕撰寫點擊事件,當按下按鈕時,下方會出現Toast的訊息。
Event Listeners 程式碼(src/ncu/bnlab/OnClickListenerExample2.java): public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button = (Button)findViewById(R.id.send); // 註冊onClick listener button.setOnClickListener(this) } public void onClick(View v) // 當按鈕被按下所需執行的動作 Toast.makeText( this, "Yes.", Toast.LENGTH_LONG).show(); 另外可以把OnClickListener作為活動的一部分來實作會更方便, 這將避免額外的問題。
Event Listeners 第二個範例中onClick()沒有回傳值,但是一些其它event listener必需回傳一個布林值。 對於其中一些callback,原因與event相關,詳細說明如下。 onLongClick() 回傳一個布林值表示是否已經處理了這個事件而不再進一步處理。 onLongClick() 回傳一個布林值表示是否已經處理了這個事件而不再進一步處理。換句話說,回傳true表示已經處理了這個事件且不繼續處理;回傳false表示還沒有處理它或這個事件應繼續交給其他on-click listener。
Event Listeners onKey() onTouch() 回傳一個布林值來表示是否已經處理了這個事件而不再進一步處理。 回傳一個布林值來表示是否已經處理了這個事件。重要的是這個事件可以有多個彼此跟隨的動作。 onKey() 回傳一個布林值來表示是否已經處理了這個事件而不再進一步處理。換句話說,回傳true表示已經處理了這個事件且不繼續處理;回傳false表示還沒有處理它或這個事件應繼續交給其他on-key listener。 onTouch() 回傳一個布林值來表示是否已經處理了這個事件。重要的是這個事件可以有多個彼此跟隨的動作。因此,如果當接收到向下動作事件時回傳false,那表示尚未處理這個事件且對後續動作也不處理。那麼,將不會被該事件中的其他動作呼叫,例如說手勢或最後出現向上動作事件。
Event Listeners 按鍵事件總是傳遞給當前焦點所在的View。 如果View當前擁有焦點,那麼可以看到事件經由dispatchKeyEvent()方法分派。 除了從View截取按鍵事件,還有一個方法,還可以在Activity中使用onKeyDown()and onKeyUp()來接收所有的事件。
Event Handlers 如果從View建立一個客製化元件,那麼將定義一些callback方法當作預設的事件處理器。 onKeyDown(int, KeyEvent) onKeyUp(int, KeyEvent) onTrackballEvent(MotionEvent) onTouchEvent(MotionEvent) onFocusChanged(boolean, int, Rect) onKeyDown(int, KeyEvent) 當一個新的按鍵事件發生時被呼叫。 onKeyUp(int, KeyEvent) 當一個向上鍵事件發生時被呼叫。 onTrackballEvent(MotionEvent) 當一個軌跡球移動事件發生時被呼叫。 onTouchEvent(MotionEvent) 當一個觸碰螢幕移動事件發生時呼叫。 onFocusChanged(boolean, int, Rect) 當View獲得或者遺失焦點時被呼叫。
Event Handlers 不屬於View類別的一部分,但可以直接影響處理事件的方式: Activity.dispatchTouchEvent(MotionEvent) ViewGroup.onInterceptTouchEvent(MotionEvent) ViewParent.requestDisallowInterceptTouchEvent(boolean) 還有一些其它方法,並不屬於View類別的一部分,但可以直接影響處理事件的方式。 所以,當在一個佈局中管理更複雜的事件時,可以考慮採用這些方法: Activity.dispatchTouchEvent(MotionEvent) 允許Activity可以在分配給視窗前取得所有的觸碰事件。 ViewGroup.onInterceptTouchEvent(MotionEvent) 允許一個ViewGroup 在分配給子View時觀察這些事件。 ViewParent.requestDisallowInterceptTouchEvent(boolean) 在一個父View之上呼叫這個方法來表示它不應該透過onInterceptTouchEvent(MotionEvent)來取得觸碰事件。
TouchMode 觸控模式(TouchMode) 設備具有觸控功能,而且使用者透過觸控來和UI互動,不需要針對焦點項目做提醒,或者設定焦點到一個特定的View,使用者也可以得知哪個項目將接受輸入。
TouchMode isFocusableInTouchMode() isInTouchMode() 對於一個具備觸控功能的裝置,當使用者觸碰螢幕,設備就會進入觸控模式。因此只有isFocusableInTouchMode()方法回傳true,View才可取得焦點。 isInTouchMode() 為了查詢目前狀態,可以呼叫isInTouchMode()來查看此裝置是否處於觸控模式中。 當使用者操作方向鍵或軌跡球瀏覽使用者介面,必需給使用者可操作的項目(例如:按鈕、下拉式選單...等等)設定焦點,這樣使用者便可以得知哪個項目將接受輸入。 然而,假使這個設備具有觸控功能,而且使用者透過觸控來和UI互動,那麼就沒必要針對焦點項目做提醒,或者設定焦點到一個特定的View。因此這種操作模式就稱為「觸控模式」。
Handling Focus Framework將根據使用者輸入去處理一般的焦點移動,這包含當View刪除、隱藏或者新View出現時改變焦點。 isFocusable() View透過isFocusable()方法表示取得焦點的意圖。 setFocusable() 呼叫setFocusable(),可以改變View是否可接受焦點。
Handling Focus isFocusableInTouchMode() setFocusableInTouchMode() 在觸控模式下,可以透過isFocusableInTouchMode()查詢一個View是否允許接受焦點。 setFocusableInTouchMode() 透過setFocusableInTouchMode(),來改變isFocusableInTouchMode()查詢的view
Handling Focus Handling Focus: <LinearLayout android:orientation="vertical" ... > <Button android:id="@+id/top" android:nextFocusUp="@+id/bottom" ... /> <Button android:id="@+id/bottom" android:nextFocusDown="@+id/top" </LinearLayout> 焦點移動基於一個在給定方向尋找最近鄰居的算法。有些的情況是,預設算法可能和開發者想要的效果不符合。在這些情況下, 可以透過覆寫下面佈局文件中的XML屬性:nextFocusDown、nextFocusLeft、nextFocusRight和nextFocusUp。 為失去焦點的View增加這些屬性,屬性值為擁有焦點的ViewID。例如本範例。 在這個直向佈局中,從第一個按鈕向上瀏覽或者從第二個按鈕向下都不會移動到其它地方。 因為這個頂部按鈕已經定義了底部按鈕為nextFocusUp (反之亦然),瀏覽焦點將從上到下和從下到上循環移動。 如果希望在使用者介面中表示一個可被當成焦點的View,可以在佈局定義中,為這個View增加android:focusable XML屬性。 將值設為true。另外還可以透過android:focusableInTouchMode在觸控模式下表示一個View為可當成焦點的。 如果想要求一個接受焦點的特定View,呼叫requestFocus()。要偵聽焦點事件(當一個View取得或者遺失焦點時被通知),則使用onFocusChange()。 透過覆寫佈局文件中的XML屬性: nextFocusDown、nextFocusLeft、nextFocusRight和nextFocusUp。 為失去焦點的View增加這些屬性,屬性值為擁有焦點的ViewID。
Q&A