上方廣告

上方連結組

2013年8月27日 星期二

Fragment 介紹


何謂 Fragment ?

用中文直譯就叫作片斷, 講白一點就是畫面的片斷, 把 Activity 畫面上指定一到多個區塊(layout), 用程式動態塞入這些我們準備好的片斷, 這就是 Fragment。

其實在 Android 2.3 之前, Android 的畫面設計向來很單純, 手機畫面也不過就是 320dp, 也塞不了多少東西, 所以對於這些動切換畫面的效果一直沒有提出很好的解決方案, 大家鼻子摸一摸多寫些程式也就過去了, 反正手機嘛, 大家都是一個畫面跳另一個畫面的, 沒什麼太大的問題。

但在 Android 3.0 時, Google 開始致力於發展平版裝置, 最明顯的不同就是畫面上現在可以塞進更多的東西。Google 希望能讓開發者更善用這些空間, 這時 Android 沒有像 iOS 一樣的 UserControl 的缺點就暴露出來了。 像是要在同一個畫面中切換不同的 Tab, 或是動態切換直向/橫向的操作,  開發者都要花更多的工在不同的地方去管理或重刻一樣的畫面。實在很麻煩。

所以, Fragment 就是 Google 提出來的解決方案 !

借用一下 google 官方網站的圖 :




以這個例子來說, 如果我們希望做到在平板時畫面設計是像左邊的圖, 在手機時畫面設計是像右邊的圖。 操作上都是點選綠色部份的 ListView 後, 在藍色的部份來觀看內容, 只是在手機時因為畫面較小, 所以點選後可以跳到另一個畫面來觀看較詳細的內容。而平板時因為畫面本來就比較大, 可以在點選 ListView 後直接觀看到所選的內容, 這樣操作上更為簡便而直覺。

這時我們就可以設計成兩個 Fragment , 在平板上就在一個 Activity 中去左邊放 FragmentA 右邊放 FragmentB。而在手機的畫面設計就使用兩個 Activity, 分別放入 FragmentA 與 FragmentB。

這樣一來, 在平板和手機在程式撰寫時的差別, 就只在於 Activity 的操作邏輯不同, 而 FragmentA 與 FragmentB 怎麼呈現就不需要因裝置不同而進行差別處理。



還可以用在哪些地方 ?

如果你可以理解上面的例子, 那麼你或許也可以理解下面這些例子

Navigation Drawer : 

像 facebook 的拉出式選單一樣, 在選單中點選不同的選項, 而呈現不同的畫面內容, 而主畫面則是不變, 也是透過 fragment 來搞定



ActionBar.Tab : 

畫面上提供幾個不同的頁籤, 點選不同的頁籤顯示不同的內容, 這也是很常見的模式, 也是透過 fragment 來做到這樣的效果


Swipe Views :
左右滑動畫面來切換上一頁或下一頁, 就是使用 ViewPager 控制項搭配 Fragment 來做到這樣的效果

當然還有很多其它地方都可以使用 Fragment, 族繁不及備載, 總之很重要就是了......

Fragment 的生命週期

Fragment 有它自己的生命週期, 只不過它的生命週期往往和它所依附的 Activity 有所相關, Google 官方的圖可以很清楚看出它生命週期的觸發順序


長得跟 Activity 的生命週期還蠻像的, 只是多了幾個陌生的事件, 其中有幾個比較值得注意的事件 :

onCreate() : 

系統建立此 fragment 時觸發。在你實作的 Fragment 物件中, 若有什麼重要元件你希望在這個 fragment 從 pause 或 stopped 狀態回恢時保留的, 請在此初始化。

onCreateView() :

這個 fragment 第一次開始畫他的 UI 畫面時觸發。 如果你想插手處理畫面, 例如在 TextView 上設定文字之類的, 可以在這個事件中處理。
回傳值是這個 fragment 根畫面的 View 物件。如果這個 fragment 沒有畫面, 則傳回 null 即可。

onActivityCreated() :

在 onCreateView() 事件之後被觸發, 表示此 fragment 所附屬的 Activity 已經準備好了。如果需要取得 Context 物件來進行處理, 可在此事件時透過 getActivity() 方法取得利用。

onPause() :

Fragment 暫停事件, 這是使用者離開這個 Fragment 的第一個跡象。雖然不代表這個 Fragment 一定會被銷毀(destroyed), 但使用者也很可能從此不再使用這個 Fragment 了。所以如果我們要儲存或保留這個 Fragment 目前的狀態的話, 通常會在這個事件中處理。


Fragment 與 Activity 間的溝通

Fragment 的初始化參數

曾經看過一些文章, 都會在 Fragment 的建構子中加上參數提供 Fragment 初始化其內容。但在 ADT 裡會對這種行為提出警告。這種警告在執行階段通常還是可以正確執行, 但為什麼會被警告呢 ?

我猜原因應該是如果我們是利用在 layout 的 xml 中描述 的標籤來建立 Fragment 的話, 系統只會呼叫 預設的建構子, 也就是不含任何參數的那種。如果你強制宣告一個需要傳遞參數的建構子, 那麼使用這種方式建立的 Fragment 就會因為找不到預設建構子而出錯。如果你是保留預設的建構子, 又再 overloading 帶參數的建構子, 那麼在使用標籤方式建立 fragment 後還是可能因為缺乏初始資料所需的物件而產生錯誤。所以 ADT 才會對此行為提出警告。

那建議的方式是什麼 ?

Activity 若是要傳遞參數給 Fragment, 可以透過 fragment.setArguments(Bundle) 的方法將要傳遞的值透過 Bundle 物件傳入 Fragment 中
而 Fragment 可以在 onCreate() 事件中透過 getArguments() 方式取回 Bundle 物件, 這樣就兩全齊美, ADT 也不會再提出警告囉!

Fragment 與 Activity 的互動與事件處理

以這篇文章最上面那張圖來舉例, 平板電腦上我想要點選 FragmentA 中的 ListView 選項, 然後在 FragmentB 中顯示我所選的內容, 該怎麼做 ?

我看過一些文章和書籍中的範例, 會先在 FragmentA 的 listView.onItemClick 事件中, 先利用 getActivity() 方法取得 Activity 的實體, 然後再透過 Activity 在指定的 layout 中找出或建立 FragmentB 的實體, 然後再把所選到的項目告訴 FragmentB 讓它呈現, 模擬器一執行, 跑得又好又順, 然後宣告問題解決了!

問題解決了嗎 ? 這裡的作法可能會有另外兩個問題......

第一個問題就是 : 不夠漂亮 ! 
在物件導向 OO 的設計原則中, 每個物件責任是愈清楚愈好。FragmentA 的責任就是呈現表單, FragmentB 的責任就是呈現內容, 多麼乾淨俐落! 但以上面的作法來說, FragmentA 除了呈現表單外, 還要負責找出 FragmentB, 然後交代 FragmentB 選到的項目。然後 ......
Activity 表示輕鬆, FragmentB 表示無壓力, FragmentA 可能因為過勞而要去看心理醫生 ....

當然漂不漂亮也是見仁見智, 丫就可以跑不然你咬我呀......

那就來看第二個情況, 這段程式可以在右邊那張圖的手機板執行嗎 ?
FragmentA 一樣處理 onItemClick 事件, 然後順利透過 getActivity() 找出 Activity 的實體, 然後再透過 Activity 實體想找出 FragmentB ...... 奶奶的熊沒有 FragmentB !!

當然沒有, 這時的 FragmentB 是放在 ActivityB 裡頭的呀.....

所以我們要在 FragmentA 中再加一個判斷, 看看如果 FragmentB 不存在的話就跳 ActivityB ..... 你的 FragmentA 累不累呀 .....

其實正經的來說我也懶得管 FragmentA 累也不累, 只是日後 FragmentA 的責任太重, 程式碼邏輯複雜, 維護會更加不易 .....

丫不然你想怎樣 !?

就像一開始說的, 我們應該讓每個物件的責任切割的清楚點。以這個例子來看,
手機版 :
FragmentA : 負責呈現 ListView, 被選取後就回報給 ActivityA 已被選取項目
ActivityA : 負責每個 Fragment 間彼此的溝通, 若 FragmentA 回報選取項目後, 開啟 ActivityB
ActivityB : 負責接收 ActivityA 傳來的選取項目, 找出 FragmentB 然後塞給它
FragmentB : 接收選取項目, 呈現資料

平板版 :
FragmentA : 負責呈現 ListView, 被選取後就回報給 ActivityA 已被選取項目
ActivityA : 負責每個 Fragment 間彼此的溝通, 若 FragmentA 回報選取項目後, 找出 FragmentB 後塞給它
FragmentB : 接收選取項目, 呈現資料

仔細看, 無論是手機還是平版, FragmentA 和 FragmentB 的責任和處理方式都是一樣的, 這樣才能達到 reuse 的效果。而 Activity 的部份, 只要分別撰寫手機版和平板版的 Actvitiy 即可將責任切割乾淨了。

另外關於 Fragment 的使用方式, 請看另一篇文章


參考資料
Android Developer
Using Fragments in Android - Tutorial

沒有留言:

張貼留言