Fragment是Android 3.0以後才出現的技術,它的主要目的是讓App的執行畫面,可以隨著手機和平板電腦的螢幕大小自動調整。例如在手機上開啟Settings功能時,畫面會先顯示主項目列表(參考附圖)。這個主項目列表其實是一個Fragment物件,當我們點選其中的項目時,畫面會切換到另一個顯示細項的Fragment物件。如果是在平板電腦上執行Settings功能,主項目列表的Fragment和顯示細項的Fragment會同時出現。這個Settings程式就是利用Fragment技術,讓程式畫面可以隨著裝置的螢幕大小,改變顯示的方式。接著我們來比較一下單純使用Activity的程式架構,和加入Fragment之後的程式架構有何差異。
(一) Activity的程式架構 vs. 加入Fragment之後的程式架構
建立App最簡單的方式是直接把介面元件建立在Activity的介面佈局檔裏頭,這種App的架構可以用下圖表示:
如果我們換成用Fragment來建立App的畫面,就變成把介面元件建立在Fragment裏頭,再把Fragment放到Activity中顯示,也就是說變成如下圖的方式:
和原來單純使用Activity的方法比較,App專案必須做以下修改:
1. 新增Fragment類別的介面佈局檔和程式檔;
2. 把原來Activity的介面佈局檔中的介面元件,搬到Fragment的介面佈局檔裏頭;
3. 把原來Activity程式檔中的程式碼,換到Fragment的程式檔裏頭;
4. 修改Activity的介面佈局檔,讓它成為Fragment物件的容器;
5. 修改Activity的程式檔,當App啟動的時候,先建立Fragment物件,再把它放到容器中顯示。
瞭解Fragment的功能和程式架構之後,接下來我們就開始介紹實作的技術細節。
(二) Fragment技術的實作
我們將Fragment的特點整理如下:
1. App的執行畫面可以由多個Fragment組成;
2. 每一個Fragment都有各自獨立的執行狀態,並且接收各自的處理事件(這個特點和Activity類似);
3. 在App執行的過程中,Fragment可以動態加入和移除。
如果想要動態改變Fragment,必須藉助FragmentManager物件。FragmentManager物件提供以下控制Fragment的方法:
1. add()
把Fragment加入App的操作畫面,通常我們是使用FrameLayout元件當成Fragment的容器。
把Fragment加入App的操作畫面,通常我們是使用FrameLayout元件當成Fragment的容器。
2. remove()
從App的操作畫面移除指定的Fragment。
從App的操作畫面移除指定的Fragment。
3. replace()
用另外一個Fragment取代目前程式畫面中的Fragment,它的功能等同於先呼叫remove()移除目前的Fragment,再呼叫add()加入另一個Fragment。
用另外一個Fragment取代目前程式畫面中的Fragment,它的功能等同於先呼叫remove()移除目前的Fragment,再呼叫add()加入另一個Fragment。
4. attach()
將之前detach的Fragment重新顯示在App的執行畫面,Fragment的畫面會重新建立。
將之前detach的Fragment重新顯示在App的執行畫面,Fragment的畫面會重新建立。
5. detach()
將指定的Fragment從程式畫面移開,這時候Fragment的畫面會被刪除,等到下次attach的時候再重新建立。
將指定的Fragment從程式畫面移開,這時候Fragment的畫面會被刪除,等到下次attach的時候再重新建立。
6. hide()
隱藏指定的Fragment。
隱藏指定的Fragment。
7. show()
顯示之前隱藏的Fragment。
顯示之前隱藏的Fragment。
8. addToBackStack()
把這個Fragment的交易(Transaction)加入系統的Back Stack,當使用者按下手機或平板電腦上的「回上一頁」按鍵時,就會回復到之前的狀態。
把這個Fragment的交易(Transaction)加入系統的Back Stack,當使用者按下手機或平板電腦上的「回上一頁」按鍵時,就會回復到之前的狀態。
9. setCustomAnimations()
設定變換Fragment時使用的動畫效果。如果使用Android系統內建的FragmentManager,必須搭配Property Animation。如果是用android.support.v4.app程式庫的FragmentManager,必須搭配View Animation。
設定變換Fragment時使用的動畫效果。如果使用Android系統內建的FragmentManager,必須搭配Property Animation。如果是用android.support.v4.app程式庫的FragmentManager,必須搭配View Animation。
10. commit()
開始執行Fragment的切換,系統是用一個非同步的process來進行,所以Fragment的切換不會立即完成。如果程式需要立刻完成Fragment的切換,可以隨後呼叫FragmentManager物件的executePendingTransactions()方法。
開始執行Fragment的切換,系統是用一個非同步的process來進行,所以Fragment的切換不會立即完成。如果程式需要立刻完成Fragment的切換,可以隨後呼叫FragmentManager物件的executePendingTransactions()方法。
動態改變Fragment的過程稱為一個Transaction,Transaction的特性是整個操作必須從頭到尾全部完成才算數,不能夠只執行其中一部份。如果不能夠從頭到尾全部完成,就要回到原來未執行Transaction之前的狀態。在使用FragmentManager切換Fragment的過程中,必須依序執行以下步驟:
Step 1. 呼叫getFragmentManager()方法取得FragmentManager。
FragmentManager fragmentMgr = getFragmentManager();
Step 2. 呼叫FragmentManager的beginTransaction()方法建立一個FragmentTransaction物件。
FragmentTransaction fragmentTrans = fragmentMgr.beginTransaction();
Step 3. 利用FragmentTransaction提供的方法控制Fragment,請參考以下程式碼範例,其中的MyFragmentA和MyFragmentB是在App專案中,已經建立好的二個Fragment類別。frameLay是在主程式介面佈局檔中的FrameLayout元件,它是用來當成Fragment的容器。
呼叫add()方法時,我們可以幫這個新加入的Fragment取一個名稱(稱為Tag,就是add()方法的第三個參數),這個Tag可以讓我們在後續的程式碼找到指定的Fragment。
MyFragmentA myFragmentA = new MyFragmentA();
MyFragmentB myFragmentB = new MyFragmentB();
MyFragmentB myFragmentB = new MyFragmentB();
fragmentTrans.add(R.id.frameLay, myFragmentA, "My fragment A");
fragmentTrans.replace(R.id.frameLay, myFragmentB, "My fragment B");
fragmentTrans.replace(R.id.frameLay, myFragmentB, "My fragment B");
Step 4. 呼叫FragmentTransaction的commit()方法,送出以上建立的Fragment處理流程,系統會根據這個處理流程來切換Fragment。但是要注意,Android系統是以執行緒(background thread)的方式來處理。如果我們希望立刻執行Fragment的切換,必須再執行下一個步驟。
fragmentTrans.commit();
(三) 實作範例
Fragment是Android 3.0以後才提供的技術,為了讓這項技術可以運用在舊的Android平台,Google特別提供一個名為android-support-v4.jar的程式庫。只要把它放到App專案中,並且讓程式使用這個程式庫裏頭的Fragment相關類別,就可以讓App在舊版的Android上執行。這個程式庫適用於Android 1.6(含)以後的裝置,也就是說只要是Android 1.6以上的手機或是平板電腦,就可以使用Fragment。以下的實作範例就是利用android-support-v4.jar程式庫來建立Fragment,我們將完成一個和Android 程式設計入門 - 學習正確的App專案開發技巧 Part 2功能完全相同的App專案。
Step 1. 執行Eclipse,建立一個Android App專案。
Step 2. 新增一個Fragment使用的介面佈局檔。在Eclipse左邊的專案檢視視窗中,用滑鼠右鍵點選App專案的res資料夾,然後從快顯功能表中選擇 New > Android XML File。在出現的對話盒中,將Resource Type欄位設定為Layout,File欄位輸入介面佈局檔的名稱,例如fragment_main,然後在下方的項目清單中點選LinearLayout,最後按下Finish按鈕。
Step 3. 新增的介面佈局檔會開啟在編輯視窗中,設計Fragment介面的方式和Activity的介面完全相同,所以我們可以運用所有學過的技巧來編輯這個介面佈局檔。我們直接複製Android 程式設計入門 - 學習正確的App專案開發技巧 Part 2App專案的介面佈局檔內容過來。
Step 4. 接下來要開始建立Fragment的程式檔,我們先展開App專案的src資料夾,再用滑鼠右鍵點選其中的「(套件路徑名稱)」資料夾,然後從快顯功能表中選擇New > Class。
Step 5. 在「新增類別」對話盒中輸入新類別的名稱,例如MainFragment,然後按下Superclass欄位最右邊的Browse按鈕,在出現的對話盒上方欄位輸入Fragment,下面的清單中會篩選出Fragment類別。在篩選出來的項目中會有二個Fragment類別,一個是在android.app套件路徑中,另一個則是在android.support.v4.app套件路徑。請選擇第二個,也就是在androidsupport.v4.app套件路徑中的Fragment,最後按下Finish按鈕。
Step 6. 在這個新類別中加入需要處理的狀態轉換方法:
Fragment和Activity一樣,二者都有各式各樣的狀態處理方法,如果要在Fragment程式檔中加入狀態處理方法,必須先將編輯游標設定到類別內部,然後按下滑鼠右鍵,從快顯功能表中選擇Source > Override/Implement Methods,在顯示的對話盒左上方勾選想要的狀態處理方法,然後按下OK按鈕。
a. onCreateView(): 這是當Fragment將要顯示在螢幕上時會執行的方法,我們必須在這個方法中設定好Fragment使用的介面佈局檔。
b.onActivityCreated(): 這是當Fragment底層的Activity被建立時會執行的方法,我們必須在這個方法中取得Fragment的介面元件,並設定給對應的介面物件,就像是之前在Activity的onCreate()中做的事情一樣。
Fragment和Activity一樣,二者都有各式各樣的狀態處理方法,如果要在Fragment程式檔中加入狀態處理方法,必須先將編輯游標設定到類別內部,然後按下滑鼠右鍵,從快顯功能表中選擇Source > Override/Implement Methods,在顯示的對話盒左上方勾選想要的狀態處理方法,然後按下OK按鈕。
Step 7. 在onCreateView()狀態處理方法中設定介面佈局檔的程式碼如下:
public class MainFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
}public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
Step 8. 複製Android 程式設計入門 - 學習正確的App專案開發技巧 Part 2App專案中的MainActivity.java程式檔中的程式碼過來:
public class MainFragment extends Fragment {
private EditText mEdtSex, mEdtAge;
private Button mBtnOK;
private TextView mTxtR;
private EditText mEdtSex, mEdtAge;
private Button mBtnOK;
private TextView mTxtR;
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
super.onActivityCreated(savedInstanceState);
View v = getView();
mEdtSex = (EditText) v.findViewById(R.id.edtSex);
mEdtAge = (EditText) v.findViewById(R.id.edtAge);
mBtnOK = (Button) v.findViewById(R.id.btnOK);
mTxtR = (TextView) v.findViewById(R.id.txtR);
mBtnOK.setOnClickListener(btnOKOnClick);
}
private View.OnClickListener btnOKOnClick = new View.OnClickListener() {
…
}
}mEdtAge = (EditText) v.findViewById(R.id.edtAge);
mBtnOK = (Button) v.findViewById(R.id.btnOK);
mTxtR = (TextView) v.findViewById(R.id.txtR);
mBtnOK.setOnClickListener(btnOKOnClick);
}
private View.OnClickListener btnOKOnClick = new View.OnClickListener() {
…
}
Step 9. 開啟主程式的介面佈局檔res/layout/activity_main.java,刪除其中的介面元件,再加入一個FrameLayout元件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
… >
… >
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Step 10. 開啟主程式檔MainActivity.java,編輯程式碼如下:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragMgr = getSupportFragmentManager();
MainFragment mainFrag = new MainFragment();
fragMgr.beginTransaction()
.add(R.id.frameLayout, mainFrag)
.commit();
}
}MainFragment mainFrag = new MainFragment();
fragMgr.beginTransaction()
.add(R.id.frameLayout, mainFrag)
.commit();
}
最後我們列出這個App專案的字串資源檔「res/values/strings.xml」:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources>
<string name="app_name">婚姻建議程式</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="sex">性別:</string>
<string name="age">年齡:</string>
<string name="btn_ok">確定</string>
<string name="result">建議:</string>
<string name="edt_sex_hint">(輸入性別)</string>
<string name="edt_age_hint">(輸入年齡)</string>
<string name="sug_not_hurry">還不急。</string>
<string name="sug_get_married">趕快結婚!</string>
<string name="sug_find_couple">開始找對象。</string>
<string name="sex_male">男</string>
</resources><string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="sex">性別:</string>
<string name="age">年齡:</string>
<string name="btn_ok">確定</string>
<string name="result">建議:</string>
<string name="edt_sex_hint">(輸入性別)</string>
<string name="edt_age_hint">(輸入年齡)</string>
<string name="sug_not_hurry">還不急。</string>
<string name="sug_get_married">趕快結婚!</string>
<string name="sug_find_couple">開始找對象。</string>
<string name="sex_male">男</string>
Step 5. 如果程式需要立刻執行Fragment的切換,可以呼叫FragmentManager的executePendingTransactions()方法。
fragmentMgr.executePendingTransactions();
沒有留言:
張貼留言