設計理念
Android3.0(API11)開始引入Fragment,主要是為了在大螢幕上更好的支援動態和靈活的UI設計。以平板為例,有足夠大的空間容納更多的元件,管理起來無疑過於麻煩。Fragment可以分割Activity的layout,並且具有自己的生命週期,從而使得元件獨立管理成為可能。正因為fragment具有獨立的生命週期,所以可以定義自己的layout和行為,從而一個fragment可以在多個activity中重複呼叫。所以我們在設計fragment的時候,要儘量追求模組化和可重用性。一般在平板和TV上普遍使用。
舉例如下:在平板上,activity使用一個fragment展示文章列表,使用另一個fragment展示文章內容;在手機上,activity使用一個fragment展示文章列表,點選跳轉到另一個activity展示。
Fragment建立
fragment的生命週期和activity非常類似,都有onCreate()、onStart()、onStop()、onPause()回撥方法。fragment一般至少要實現三個回撥方法:
- onCreate():fragment建立時呼叫,主要初始化一些重要的元件
- onCreateView():fragment繪製介面時呼叫(setContentView(id)),如果fragment有介面的話需要返回一個view;如果fragment沒有UI,直接返回空即可
- onPause():使用者離開fragment,一般要儲存資料(使用者可能不會再回來)
fragment型別
常見的有三種fragment,分別是DialogFragment、ListFragment、PreferenceFragment
- DialogFragment:展示浮動對話方塊
- ListFragment:展示一列資料
- PreferenceFragment:以列表形式展示首選項物件的層級關係
建立fragment
fragment一般具有自己的UI,併成為activity UI的一部分。為了給fragment建立UI,必須實現onCreateView()回撥方法,並返回一個view(fragment的根layout)。
如果fragment是ListFragment的子類,onCreateView()預設返回ListView,不需要重寫。
public static class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
}複製程式碼
container是activity的layout,fragment的layout將會被插入其中;savedInstanceState即儲存的fragment被異常銷燬前的狀態;inflate()有三個引數:
- id:flagment的view的資源id
- container:activity的layout,fragment的layout將插入其中
- false:false表示fragment僅為container提供layout 引數(動態新增、刪除、替代fragment時使用);true表示container成為fragment layout的父view(在activity佈局檔案中定義fragment使用)
把fragment新增到activity中
一般來說,fragment的layout會被嵌入成為activity view層級的一部分。有兩種方式可以把fragment新增到activity的layout中:1.靜態載入;2.動態載入。
靜態載入:在activity layout中宣告fragment,程式碼如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.example.chaos.myapplication.MainActivity" tools:showIn="@layout/activity_main"> <fragment android:id="@+id/fragment_person_list" android:name="com.example.chaos.myapplication.PersonListFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/fragment_person_detail" android:name="com.example.chaos.myapplication.DetailFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>複製程式碼
系統建立該activity的layout時,會將指定的fragment全部例項化並分別呼叫他們的onCreateView()回撥。然後檢索fragment的layout,並將onCreateView()返回的view取代
<fragment>
元素直接插入activity的layout中。
activity重啟時,需要恢復fragment的資料,這就要求fragment要有個唯一的識別符號(否則無法管理fragment)。有三種方式可以為fragment賦予唯一的id
- 使用android:id屬性賦予id
- 使用android:tag屬性賦予唯一標籤
- 如果id和tag都沒提供的話,系統模式使用container view的id
2.. 動態載入:動態新增fragment到ViewGroup中
只要在activity在執行,都可以新增fragment到activity的layout中,只需要指定ViewGroup存放fragment就行。
為了在activity中操作fragment的事務(add、remove、replace),必須使用FragmentTransaction()。程式碼如下:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();複製程式碼
新增fragment使用add()方法,指定ViewGroup和要新增的fragment,對fragmentTransaction做的任何改動都要執行commit()方法才能生效。
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();複製程式碼
新增一個沒有UI的fragment
並非所有fragment都必須有UI,fragment也可以用來執行後臺操作,此時,無需UI也可以。該如何新增一個沒有UI的fragment呢?
首先,需要先通過findFragmentByTag()找到,因為tag是唯一能標識它的(只能通過add(fragment,string)主動設定tag)。
如果findFragmentByTag()返回null的話,需要通過add(fragment,string)為fragment主動設定tag並新增。
Fragment管理
fragment的管理是由FragmentManager實現的,可以在activity中通過getFragmentManager()獲取FragmentManager。FragmentManager的功能如下:
- 使用findFragmentById()(針對在activity layout提供一個UI的fragment)和findFragmentByTag()(針對沒有UI的fragment)獲取activity中已存在的fragment
- 呼叫popBackStack()將fragment從返回棧彈出(類似於使用者點選back)
- 呼叫addOnBackStackChangedListener()註冊監聽器監聽返回棧的變化
靜態載入的fragment一定具有id或tag;動態載入的id一定具有container view的id或tag(fragment如果沒有唯一識別符號,則無法進行有效管理),動態載入一共有三個方法:
add(Fragment fragment, String tag)
:適用於有UI和無UI的fragmentadd(@IdRes int containerViewId, Fragment fragment)
:適用於有UI的fragmentadd(@IdRes int containerViewId, Fragment fragment,String tag);
:適用於有UI的fragment所以沒有UI的fragment,只能通過findFragmentByTag()獲取;有UI的fragment可以通過id或tag獲取。
也可以使用FragmentManager開啟FragmentTransaction,從而操作fragment的事務,如新增或移除。
Fragment事務處理
一個事務就是一系列變化,事務具有原子性、一致性、隔離性、永續性。可以使用FragmentTransaction實現事務,也可以把事務放到由activity管理的返回棧中,便於使用者返回之前的fragment的狀態.
可以從FragmentManager獲取FragmentTransaction例項
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();複製程式碼
FragmentTransactioni可以藉由add()、remove()、replace()實現對事務的操作,而且每次操作必須實現commit()才能生效。
如果想把fragment加入到返回棧,以便使用者退回到fragment原先的狀態的話,需要在commit()之前呼叫addBackStack()方法:
DetailFragment detailFragment = new DetailFragment();
fragmentTransaction.add(detailFragment,"");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();複製程式碼
為了檢索返回棧中的fragment,必須監聽onBackPressed()事件,否則的話如果棧中存在其他activity的話,就會直接返回到其他activity中;如果沒有activity的話,就會直接退出應用。
@Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0){
getFragmentManager().popBackStack();
}else {
super.onBackPressed();
}
}複製程式碼
新增改變到Transaction中的順序無關緊要,除非:
- 最後呼叫了commit()方法
- 新增了多個改變到同一個container中,新增的順序決定了container中view層級的順序
如果沒有呼叫addBackStack(),當remove一個fragment時,commit之後會立刻銷燬,使用者無法返回;如果新增到返回棧中,remove一個fragment時,commit之後會處於stop狀態,使用者可以返回到之前的狀態(resume)
commit()這個操作執行在主執行緒中,也就意味著呼叫commit()之後要排序執行,並不會立刻執行事務。如果想立刻執行的話,可以在commit()之前呼叫excutePendingTransactions(),不過一般並無此必要。
commit()必須在onSaveInstanceState()之前呼叫,因為在此之後,activity不會儲存資料,導致commit()呼叫會丟擲異常;如果允許狀態資料丟失的話,可以呼叫 commitAllowingStateLoss()。
與Activity的通訊
雖然fragment作為物件的實現和activity存在很大不同,但是作為activity的一部分,fragment又和activity緊密聯絡。
- 從fragment中呼叫activity
View listView = getActivity().findViewById(R.id.list);
- 從activity中呼叫fragment
DetailFragment detailFragment1 = (DetailFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_person_detail);複製程式碼
為activity建立介面
常見的模型:activity包含兩個fragment,左邊fragment顯示標題列表,右邊fragment顯示內容。點選左側的fragment的標題,更新右側的fragment內容。實現方式就是在左邊fragment建立介面,並在介面中實現傳值方法,activity實現該介面,監聽方法,然後將接受到的值傳給右邊的fragment。
fragment建立介面和方法:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}複製程式碼
把activity轉換成介面,從而實現activity監聽
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
...
}複製程式碼
在fragment的onListItemClick事件中呼叫傳值方法給activity
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Append the clicked item's row ID with the content provider Uri
Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
// Send the event and Uri to the host activity
mListener.onArticleSelected(noteUri);
}
...
}複製程式碼
Fragment生命週期
和activity一樣,fragment也有三種基本狀態:
- 執行態:fragment在正在執行的activity中處於可見狀態
- 暫停態:另一個activity位於前臺並獲取焦點,fragment所在的activity仍然可見(前臺activity半透明或沒有全部覆蓋fragment所在activity)
- 停止態:fragment所在activity處於stop狀態,或者fragment被remove掉,但是新增到返回棧中了。處於暫停態的fragment仍然存活(系統仍然保留狀態資訊和成員變數),但是已不再可見,且activity被銷燬時,fragment也會被銷燬。
fragment和activity生命週期的一個最大差別在於二者在返回棧中的儲存。activity的返回棧是由系統管理的;fragment的返回棧是由activity管理的(當fragment被remove時,呼叫addBackStack())。
fragment如果需要context物件的話,可以呼叫getActivity(),但是這個方法要在onAttach()之後、onDetach()之前呼叫才有效,否則將會返回null。