上方廣告

上方連結組

2013年8月29日 星期四

如何使用 Fragment

在前一篇文章 Fragment 介紹 中我講了一大堆什麼是 Fragment, 該用在哪, 生命週期, 如何設計等等 ......。但就是沒跟大家說要怎麼用!? 身為一個以 copy + paste 然後改改改就產出為最高指導原則的工程師這種寫法當然是不被允許的, 所以這篇來說一下怎麼使用 Fragment 這種神奇的好物!

其實 Fragment 的實作上步驟都是差不多的 :

第一步 : 實作繼承自 Fragment 的類別
package com.example;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MyFragment extends Fragment {
   ......
} 




第二步 : 若是想要載入自己設計好的畫面, 在 res/layout 資料夾中新增一個 Layout 的 XML 檔, 例如 fragment_a.xml

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    
        <TextView android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
 </LinearLayout>
 

第三步 : 在 onCreateView() 中透過 LayoutInflater 載入
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_a,
        container, false);
    return view;
  }

第四步 : 將訂好的 Fragment 物件放到 Activity 的畫面中
這裡有兩種不同的作法, 第一種是直接用 的標籤將 fragment 直接放在 Activity 指定的地方


 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    
        <fragment android:id="@+id/myFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"        
        android:name="com.example.MyFragment" />   
  </LinearLayout>
 

第二種是在 Activity 的 layout 中不直接放入 而是放置一個

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    
        <FrameLayout android:id="@+id/layout_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent" />
  </LinearLayout>
 
然後再由 Activity 的程式中將 Fragment 放置於指定 layout 中
FragmentManager fm = getFragmentManager();
MyFragment fragment = (MyFragment) fm.findFragmentById(R.id.layout_fragment);
if (fragment==null || ! fragment.isInLayout()) {
   fragment = new MyFragment();
   FragmentTransaction ft = fm.beginTransaction();
   ft.replace(R.id.layout_fragment, fragment);
   ft.commit();
}
這兩種方式的效果看起來差不多, 不過我個人比較偏愛後者, 原因如下 :
1. 前者的方式 fragment 是一開始就嵌死在 Activity 中的, 後者可動態寫在任何事件中, 較靈活。
2. 前者的 fragment 種類無法抽換, 後者可以動態決定要使用哪個 Fragment。
3. 前者至少必須使用 Android API Level 11 以上, 後者有在 Support Library 中可向前相容。

讓我們來認識幾個重要的元件

FragmentManager

故名思義, FragmentManager 是管理 Activity 與 Fragment 互動的類別, 可以在 Activity 中透過 getFragmentManager() 方式取得。最常用的幾個方法

Fragment findFragmentById(int id) :

透過指定 Layout 的 resource id 來取得該 Layout 中存在的 fragment 實體。

FragmentTransaction beginTransaction() :

傳回 FragmentTransaction 物件, 表示要開始一系列 Fragment 與 FragmentManager 間的互動。

FragmentTransaction

一般我們工程師看到 Transaction 這個詞, 就會聯想到資料庫的 Transaction。也就是一次執行多個動作, 然後最後執行送交(commit)後要嘛一次全部執行完畢, 要不然就是中途發生錯誤我們可以回復(rollback)到最初始的資料狀態 , 不會有執行到一半的中間狀態。其實 FragmentTransaction 也差不多是這個意思, 我們可以交代給這個物件一系列的動作請他執行, 只是這些動作相對單純也不需要做資料回復, 所以少了 rollback 這樣的機制。

我們看一下 FragmentTransaction 常執行哪些動作 :

FragmentTransaction add(int containerViewId, Fragment fragment) :

將指定的 fragment 加入指定的 container 中, 這裡的 container 其實指的就是 Layout)。

FragmentTransaction remove(Fragment fragment) :

移除指定的 fragment, 此 fragment 內的所有狀態也會一併被刪除, 也就是這個 fragment 已不能再被使用了。如果這個 fragment 已經被加入某個 container 中, 則也會從該 container 中一併被清除。

FragmentTransaction detach(Fragment fragment) : 

將指定的 fragment 從其所依附的 container 中移除。與 remove 方法不同的是, 該 fragment 的狀態仍會保留, 這個實體可以繼續透過 attach 方式使用。
另外要注意的是, 當我們在程式中使用 detach 方式將 fragment 從畫面上拿下來, 也會一併將此 fragment 從 back stack 的階層中移除。

FragmentTransaction attach(Fragment fragment) : 

將被 detach 過的 fragment 重新裝回原本的 container 中顯示, 當然也會將此動作加回 back stack 階層中。

FragmentTransaction replace(int containerViewId, Fragment fragment) :

將指定的 container 中存在的 fragment 進行 remove() 動作, 然後將指定的 fragment 透過 add() 的方式裝回該 container 中。
要注意的是這裡是 remove + add, 而不是 detach + attach, 所以被移除的 fragment 是不能再繼續使用的。

FragmentTransaction addToBackStack(String name) :

將這個 Transaction 所執行的動作在 commit 後加入 back stack 階層中, 也就是當使用者按下返回鍵時, 這一連串的動作都會被復原。

FragmentTransaction setCustomAnimations(int enter, int exit) :

當開始執行這個 Transaction 時所使用的動畫效果(animation), enter 與 exit 的數字是指該動畫在 resource 中的 id 編號。

int commit() : 

送交執行。但其實也不一定就會馬上被執行, 它會先去排隊, 等到主執行緒有空的時候才會執行。說是這樣說啦, 但除非主執行緒卡太多需要長時間的工作 (例如 ImageView 中指定 src 來自於一個 URL 之類的), 不然一般來說當我們呼叫 commit() 時幾乎都是會立刻反應在畫面上。

在這邊附上一個範例, 可以做到兩個 Button 按下時切換不同的 Fragment, 有興趣的話可以下載回去看看
SimpleFragmentSample 範例






向前相容

向前相容總是個很讓人玩味的課題, 雖然說 Android 3.0 以後的版本到目前為止(2013年8月)已經佔了 Android 總市佔的 63.1% (資料來源)。但工程師總是會被要求產品要想辦法支援到另外的那30%呀 .....。所以就會發現雖然有愈來愈多的 API 出來, 但大多數的專案都還是得用舊的寫法, 或者必須使用有相容的 API 在開發。

Google 很有善意的提供了很有名的 android-support-v4.jar 可以做到向前相容。但這裡的向前相容只是做到『本質不同, 但效果一樣』。舉例 Fragment 的例子來說, 原生的 library 中所使用的類別是 android.app.Fragment 類別, 但 support library 中所提供的卻是 android.support.v4.app.fragment 類別。這兩個類別所提供的方法幾乎一模一樣, 但兩者間並沒有任何繼承或共用的介面, 所以無法轉型。

在 Fragment 的向前相容部份, 其實我們只要做以下幾個小改變 : 
  1. 原繼承自 android.app.Fragment 的類別, 改成繼承自 android.support.v4.app.Fragment 類別。
  2. 管理 Activity 的類別, 從原本繼承自 android.app.Activity 類別, 改成繼承自 android.support.v4.app.FragmentActivity 類別。
  3. 取出 FragmentManager 的方法, 從原本使用 getFragmentManager(), 改成 getSupportFragmentManager()
  4. 而回傳的 android.app.FragmentManager 類別, 改成 android.support.v4.app.FragmentManager 類別。
  5. 原本的 android.app.FragmentTransaction 類別, 改成 android.support.v4.app.FragmentTransaction 類別。
以上除了 1,2,3 需要稍微動手修改之外, 改好後 4,5兩項只需按個 ctrl+shift+o 就可以改完了。一整個方便。

不過經驗上是, 就算 Fragment 可以往前相容了, 還是很可能遇到其它搭配的 API 讓你面臨到可能無法向前相容的窘境。例如 ActionBar .....。

Fragment 的子類別

Fragment 就像是張白紙, 裡頭要塞什麼自己可以決定, 非常自由。但有許多的時候一個 Fragment 可能就只放一個控制項, 而這種情境又非常常出現 (例如一個 Fragment 裡頭只放一個 ListView), 而我們還是得設定好一個 layout 的 xml 檔。然後想辦法透過 id 把 control 取出來塞值, 既花時間又花精神。

所以 Android 官方就提供了幾個子類別, 讓你直接繼承這些子類別就可以使用了, 包含了 :
  1. ListFragment : 裡頭預設就有 ListView, 並提供方法與事件存取或控制 ListView。
  2. DialogFragment : 包含了一個 Dialog 物件, 並提供控制 Dialog 的方法。
  3. PreferenceFragment : 裡頭預設提供幾個方法讓你輕鬆存取 SharedPreference。
  4. WebViewFragment : 和 ListFragment 的意思差不多, 裡頭就放了一個 WebView。
這些子類別怎麼使用在這我就不多說了, 只是提出來讓大家知道有這些東西可用就好, 有興趣的可以自己研究一下。要不然就得等到哪天我有空再寫了 .....。

參考資料
Building a Dynamic UI with Fragments
Fragment
FragmentManager
FragmentTransaction







沒有留言:

張貼留言