Android學習筆記系列五 —— Fragments板塊

Fragments


Fragment必須老是被嵌入到一個activity之中,而且fragment的生命週期直接受其宿主activity的生命週期的影響。例如,一旦activity被暫停,它裏面全部的fragment也被暫停,一旦activity被銷燬,它裏面全部的fragment也被銷燬。然而,當activity正在運行時(處於resumed的生命週期狀態),你能夠單獨的操控每一個fragment,好比添加或者刪除。當你執行這樣一項事務時,能夠將它添加到後臺的一個棧中,這個棧由activity管理着——activity裏面的每一個後臺棧內容實體是fragment發生過的一條事務記錄。這個後臺棧容許用戶經過按BACK鍵回退一項fragment事務(日後導航)。php

當你添加一個fragment做爲某個activity佈局的一部分時,它就存在於這個activity視圖體系內部的ViewGroup之中,而且定義了它本身的視圖佈局。你能夠經過在activity佈局文件中聲明fragment,用<fragment>元素把fragment插入到activity的佈局中,或者是用應用程序源碼將它添加到一個存在的ViewGroup中。然而,fragment並非一個定要做爲activity佈局的一部分;fragment也能夠爲activity隱身工做。html

設計原理


Android在3.0這個版本中(API Level 「Honeycomb」)引入了fragment的概念,主要是支持在大屏幕上更爲動態和靈活的UI設計。java

例如新聞應用程序將一個fragment放在左邊顯示文章列表,在右邊用另外一個fragment來顯示一篇文章——兩個fragment在同一個activity中並排着,而且每一個fragment都有其本身的生命週期回調方法序列用以處理各自的用戶輸入事件。所以,用戶能夠在同一個activity中選擇和閱讀文章,而不是在一個activity中選擇,在另外一個activity中閱讀。android

應該將每個fragment設計爲模塊化的和可複用化的activity組件。也就是說,你能夠在多個activity中引用同一個fragment,由於fragment定義了它本身的佈局,而且使用它自己生命週期回調的行爲。這點尤其重要,由於它讓你可以改變fragment組合以知足不一樣的屏幕尺寸。在設計同時支持平板電腦和手機的應用時,經過不一樣的佈局配置能夠複用fragments,基於可以使用的屏幕空間優化用戶體驗。例如,在手機上,當同一個activity不能容納更多的fragment時,可能須要經過分離fragments提供一個單區域的界面。api

例如——仍是以那個新聞應用程序爲例——當程序運行在平板尺寸屏幕設備上時,能夠在Activity A中嵌入兩個fragment。可是,當運行在手機尺寸屏幕上,就沒有足夠的空間容納兩個fragment了,所以Activity A只能引用包含文章列表的fragment,在當用戶選擇一篇文章時,能夠啓動Activity B,它包含了用來閱讀文章的第二個fragment。這樣,應用程序經過以不一樣組合的複用fragments支持了平板電腦和手機。app

建立Fragment


Fragment lifecycle.png

要建立一個fragment,必須建立一個fragment的子類(或是繼承自它的子類),通常狀況下,你至少須要實現如下幾個生命週期方法:ide

  • onCreate()    在建立fragment時系統會調用此方法。在實現代碼中,你能夠初始化想要在fragment中保持的那些必要組件,當fragment處於暫停或者中止狀態以後可從新啓用它們。模塊化

  • onCreateView()    在第一次爲fragment繪製用戶界面時系統會調用此方法。爲fragment繪製用戶界面,這個函數必需要返回所繪出的fragment的根View。若是fragment沒有用戶界面能夠返回空。函數

  • onPause()    系統回調用該函數做爲用戶離開fragment的第一個預兆(儘管這並不總意味着fragment被銷燬)。在當前用戶會話結束以前,一般要在這裏提交任何應該持久化的變化(由於用戶可能再也不返回)。佈局

除了基類fragment,這裏還有幾個你可能會繼承的子類:

  • DialogFragment顯示一個浮動的對話框。使用這個類建立對話框是使用Activity類對話框幫助方法以外的另外一個不錯的選擇,由於你能夠把fragment對話框併入到由activity管理的fragments後臺棧中,容許用戶返回到一個已經摒棄的fragment。

  • ListFragment顯示一個由適配器管理的條目列表(例如SimpleCursorAdapter),相似於ListActivity。而且提供了許多管理列表視圖的函數,例如處理點擊事件的onListItemClick()回調函數。

  • PreferenceFragment顯示一個Preference對象的體系結構列表,相似於preferenceActivity。這在爲應用程序建立「設置」activity時是很實用的。

添加用戶界面

fragment常被用做activity用戶界面的一部分,而且將自己的佈局構建到activity中去。

爲了給fragment提供一個佈局,你必須實現onCreateView()回調函數,在繪製fragment佈局時Android系統會調用它。實現這個函數時須要返回fragment所屬的根View。

注意:若是你的fragment是ListFragment的子類,默認實現從onCreateView()返回一個ListView,因此你不須要實現它。

爲了從onCreateView()返回一個佈局,你能夠從layout resource定義的XML文件inflate它。爲了便於你這樣作,onCreateView()提供一個LayoutInflater對象。

例如,下面是一個fragment子類,它的佈局從example_fragment.xml載入的:

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);
		}
	}

傳入onCreateView()的參數container 是你的frament佈局將要被插入的父ViewGroup(來自activity的佈局)。若是fragment處於resumed狀態(恢復狀態在操縱fragment生命週期一節中將做更多討論),參數savedInstanceState是屬於Bundle類,它提供了fragment以前實例的相關數據。

  • inflate()函數須要如下三個參數:

    • 要inflate的佈局的資源ID。

    • 被inflate的佈局的父ViewGroup。傳入container很重要,這是爲了讓系統將佈局參數應用到被inflate的佈局的根view中去,由其將要嵌入的父view指定。

    • 一個布爾值,代表在inflate期間被infalte的佈局是否應該附上ViewGroup(第二個參數)。(在這個例子中傳入的是false,由於系統已經將被inflate的佈局插入到容器中(container)——傳入true會在最終的佈局裏建立一個多餘的ViewGroup。)

將fragment添加到activity之中

一般,fragment構建了其宿主activity的部分界面,它被做爲activity所有視圖層次體系的一部分被嵌入進去。在acitivity佈局中添加fragment有兩種方法:

    • 在activity的佈局文件裏聲明fragment

  • 像這樣,你能夠像爲view同樣爲fragment指定佈局屬性。例如,下面含有兩個fragment的佈局文件:

<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
		android:orientation="horizontal"
		android:layout_width="match_parent"
		android:layout_height="match_parent">
		<fragment android:name="com.example.news.ArticleListFragment"
				android:id="@+id/list"
				android:layout_weight="1"
				android:layout_width="0dp"
				android:layout_height="match_parent" />
		<fragment android:name="com.example.news.ArticleReaderFragment"
				android:id="@+id/viewer"
				android:layout_weight="2"
				android:layout_width="0dp"
				android:layout_height="match_parent" />
	</LinearLayout>
  • <fragment>中的android:name 屬性指定了佈局中實例化的Fragment類。

當系統建立activity佈局時,它實例化了佈局文件中指定的每個fragment,併爲它們調用onCreateView()函數,以獲取每個fragment的佈局。系統直接在<fragment>元素的位置插入fragment返回的View。

注意:每一個fragment都須要一個惟一的標識,若是重啓activity,系統可用來恢復fragment(而且可用來捕捉fragment的事務處理,例如移除)。爲fragment提供ID有三種方法:

  • 用android:id屬性提供一個惟一的標識。

  • 用android:tag屬性提供一個惟一的字符串。

  • 若是上述兩個屬性都沒有,系統會使用其容器視圖(view)的ID。

    • 或者,經過編碼將fragment添加到已存在的ViewGroup中

在activity運行的任什麼時候候,你均可以將fragment添加到activity佈局中。你僅須要簡單指定用來放置fragment的ViewGroup。

你應當使用FragmentTransaction的API來對activity中的fragment進行操做(例如添加,移除,或者替換fragment)。你能夠像下面這樣從Activity中取得FragmentTransaction的實例:

FragmentManager fragmentManager = getFragmentManager()
	FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

能夠用add()函數添加fragment,並指定要添加的fragment以及要將其插入到哪一個視圖(view)之中:

ExampleFragment fragment = new ExampleFragment();
	fragmentTransaction.add(R.id.fragment_container, fragment);
	fragmentTransaction.commit();

傳入add()函數的第一個參數是fragment被放置的ViewGroup,它由資源ID(resource ID)指定,第二個參數就是要添加的fragment。

一旦經過FragmentTransaction 作了更改,都應當使用commit()使變化生效。

添加無界面的fragment

上面的例子是如何將fragment添加到activity中去,目的是提供一個用戶界面。然而,也可使用fragment爲activity提供後臺動做,卻不呈現多餘的用戶界面。

想要添加沒有界面的fragment ,可使用add(Fragment, String)(爲fragment提供一個惟一的字符串「tag」,而不是視圖(view)ID)。這樣添加了fragment,可是,由於尚未關聯到activity佈局中的視圖(view) ,收不到onCreateView()的調用。因此不須要實現這個方法。

爲無界面fragment提供字符串標籤並非專門針對無界面fragment的——也能夠爲有界面fragment提供字符串標籤——可是對於無界面fragment,字符串標籤是惟一識別它的方法。若是以後想從activity中取到fragment,須要使用findFragmentByTag()。

activity使用fragment在後臺運行可參考例子FragmentRetainInstance.java

管理Fragments


想要管理activity中的fragment,可使用FragmentManager。能夠經過在activity中調用getFragmentManager()得到。

使用FragmentManager 能夠作以下事情,包括:

  • 使用findFragmentById()(用於在activity佈局中提供有界面的fragment)或者findFragmentByTag()獲取activity中存在的fragment(用於有界面或者沒有界面的fragment)。

  • 使用popBackStack()(模仿用戶的BACK命令)從後臺棧彈出fragment。

  • 使用addOnBackStackChangedListener()註冊一個監聽後臺棧變化的監聽器。

關於這些函數和其它的更多信息,可參考FragmentManager類文檔。

處理Fragment事務


在activity中使用fragment的一大特色是具備添加、刪除、替換,和執行其它動做的能力,以響應用戶的互動。提交給activity的每一系列變化被稱爲事務,而且能夠用FragmentTransaction 中的APIs處理。你也能夠將每個事務保存在由activity管理的後臺棧中,而且容許用戶導航回退fragment變動(相似於activity的導航回退)。

你能夠從FragmentManager中獲取FragmentTransaction實例,像這樣:

 FragmentManager fragmentManager = getFragmentManager();
 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每項事務是在同一時間內要執行的一系列的變動。你能夠爲一個給定的事務用相關方法設置想要執行的全部變化,例如add(),remove(),和replace()。而後,用commit()將事務提交給activity。

然而,在調用commit()以前,爲了將事務添加到fragment事務後臺棧中,你可能會想調用addToBackStatck()。這個後臺棧由activity管理,而且容許用戶經過按BACK鍵回退到前一個fragment狀態。

舉個例子,下面的代碼是如何使用另外一個fragment代替一個fragment,而且將以前的狀態保留在後臺棧中:

 // Create new fragment and transaction
 Fragment newFragment = new ExampleFragment();
 FragmentTransaction transaction = getFragmentManager().beginTransaction();

 // Replace whatever is in the fragment_container view with this fragment,
 // and add the transaction to the back stack
 transaction.replace(R.id.fragment_container, newFragment);
 transaction.addToBackStack(null);

 // Commit the transaction
 transaction.commit();

在這個例子中,newFragment替換了當前在佈局容器中用R.id.fragment_container標識的全部的fragment(若是有的話),替代的事務被保存在後臺棧中,所以用戶能夠回退該事務,可經過按BACK鍵還原以前的fragment。

若是添加多個變動事務(例如另外一個add()或者remove())並調用addToBackStack(),那麼在調用commit()以前的全部應用的變動被做爲一個單獨的事務添加到後臺棧中,而且BACK鍵能夠將它們一塊兒回退。

將變動添加到FragmentTransaction中的順序注意如下兩點:

  • 必需要在最後調用commit()

  • 若是你正將多個fragment添加到同一個容器中,那麼添加順序決定了它們在視圖層次(view hierarchy)裏顯示的順序。

在執行刪除fragment事務時,若是沒有調用addToBackStack(),那麼事務一提交fragment就會被銷燬,並且用戶也沒法回退它。然而,當移除一個fragment時,若是調用了addToBackStack(),那麼以後fragment會被中止,若是用戶回退,它將被恢復過來。

提示:對於每個fragment事務,在提交以前經過調用setTransition()來應用一系列事務動做。

調用commit()並不馬上執行事務,相反,而是採起預定方式,一旦activity的界面線程(主線程)準備好即可運行起來。然而,若是有必要的話,你能夠從界面線程調用executePendingTransations()當即執行由commit()提交的事務。但這樣作,一般是沒有必要的,除非其它線程的工做依賴與該項事務。

警告:只能在activity保存狀態(當用戶離開activity時)以前用commit()提交事務。若是你嘗試在那時以後提交,會拋出一個異常。這是由於若是activity須要被恢復,提交後的狀態會被丟失。對於這類丟失提交的狀況,可以使用commitAllowingStateLoss()

與Activity交互


儘管Fragment被實現爲一個對象,它獨立於Activity並能夠在多個Activity中使用,一個給定的fragment實例直接被捆綁在包含它的Activity中。

特別是,fragment能夠經過getActivity()函數訪問Activity,而且很容易的執行相似於查找activity佈局中的視圖的任務:

 View listView = getActivity().findViewById(R.id.list);

一樣的,activity可以調用fragment的函數findFragmentById()或者findFragmentByTag(),從FragmentManager中獲取Fragment的索引,例如:

 ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

建立activity事件回調函數

在一些狀況下,你可能須要fragment與activity共享事件。這樣作的一個好方法是在fragment內部定義一個回調接口,並須要宿主activity實現它。當activity經過接口接收到回調時,能夠在必要時與佈局中的其它fragment共享信息。

舉個例子,若是新聞應用的actvity中有兩個fragment——一個顯示文章列表(fragment A),另外一個顯示一篇文章(fragment B)——而後fragment A 必需要告訴activity列表項什麼時候被選種,這樣,activity能夠通知fragment B顯示這篇文章。這種狀況下,在fragment A內部聲明接口OnArticleSelectedListener:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...}

而後fragment的宿主activity實現了OnArticleSelectedListener接口,而且重寫onArticleSelected()以通知fragment B來自於fragment A的事件。爲了確保宿主activity實現了這個接口,fragment A的onAttach()回調函數(當添加fragment到activity中時系統會調用它)經過做爲參數傳入onAttach()的activity的類型轉換來實例化一個OnArticleSelectedListener實例。

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");
        }
    }
    ...}

若是activity沒有實現這個接口,那麼fragment會拋出一個ClassCaseException異常。一旦成功,mListener成員會保留一個activity的OnArticleSelectedListener實現的引用,由此fragment A能夠經過調用由OnArticleSelectedListener接口定義的方法與activity共享事件。例如,若是fragment A是ListFragment的子類,每次用戶點擊列表項時,系統都會調用fragment的onListItemClick()事件,而後fragment調用onArticleSelected()來與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);
    }
    ...}

傳遞給onListItemClick()的參數id是點擊的列表項行id,activity(或者其它fragment)用以從應用的ContentProvider獲取文章。

關於使用content provider的更多資料能夠在在Content Providers文檔獲取。

添加items到Action Bar

你的fragments能夠經過實現onCreateOptionsMenu()來構建菜單項到activity的Options Menu(所以Action Bar也同樣)。爲了使用這個方法接收到調用,無論怎樣,你必須在onCreate()期間調用setHasOptionsMenu(),來指明想要添加項目到Options Menu的那個fragment(不然,fragment將接收不到onCreateOptionsMenu()的調用)。

任何想要在fragment中的Options Menu添加的項目都追加到已有的菜單項後面。當菜單項被選中時,fragment也會接收到對onOptionsItemSelected()的回調。

你也能夠經過調用registerForContextMenu()在fragment佈局中註冊一個view以提供一個context menu。當用戶打開context menu時,fragment接收到對onCreateContextMenu()的回調。當用戶選中一個項目時,fragment接收到對onContextItemSelected()的回調。

注意:儘管你的fragment會接收到爲添加到每一個菜單項被選擇菜單項的回調,但當用戶選擇一個菜單項時,activity會首先接收到對應的回調。若是activity的選擇菜單項回調的實現沒有處理被選中的項目,那麼該事件被傳遞給fragment的回調。這一樣適用於Options Menu和context menu。

處理Fragment生命週期


Activity fragment lifecycle.png

管理fragment生命週期與管理activity生命週期很相像。像activity同樣,fragment也有三種狀態:

  • Resumed    fragment在運行中的activity可見。 

  • Paused    另外一個activity處於前臺且獲得焦點,可是這個fragment所在的activity仍然可見(前臺activity部分透明,或者沒有覆蓋全屏)。 

  • Stopped    fragment不可見。要麼宿主activity已經中止,要麼fragment已經從activity上移除,但已被添加到後臺棧中。一箇中止的fragment仍然活着(全部狀態和成員信息仍然由系統保留着)。可是,它對用戶來說已經再也不可見,而且若是activity被殺掉,它也將被殺掉。

同activity相似的還有,你也能夠用Bundle保存fragment狀態,萬一activity的進程被殺掉了,而且在activity被從新建立時,你須要恢復fragment狀態。在回調執行fragment的onSaveInstanceState()期間能夠保存狀態,在onCreate(),onCreateView(),或者onActvityCreate()中能夠恢復狀態。更多關於保存狀態的信息,參考Activities文檔。

在生命週期方面,activity與fragment之間一個很重要的不一樣,就是在各自的後臺棧中是如何存儲的。當activity中止時,默認狀況下,activity被安置在由系統管理的activity後臺棧中(所以用戶能夠按BACK鍵回退導航,就像在Tasks和後臺棧中討論的那樣)。可是,僅當你在一個事務被移除時,經過顯式調用addToBackStack()請求保存的實例,該fragment才被置於由宿主activity管理的後臺棧。

除此以外,管理fragment的生命週期與管理activity的生命週期很是類似。因此,管理activity生命週期的實踐一樣也適用於fragment。你須要瞭解的,僅僅是activity的生命週期如何影響fragment的的。

與activity生命週期的協調合做

fragment所生存的activity生命週期直接影響着fragment的生命週期,由此針對activity的每個生命週期回調都會引起一個fragment相似的回調。例如,當activity接收到onPause()時,這個activity之中的每一個fragment都會接收到onPause()。

Fragment有一些額外的生命週期回調方法,然而,爲了處理像是建立和銷燬fragment界面,它與activity進行獨特的交互。這些額外的回調方法是:

  • onAttach()    當fragment被綁定到activity時調用(Activity會被傳入)。 

  • onCreateView()    建立與fragment相關的視圖體系時被調用。 

  • onActivityCreated()    當activity的onCreate()函數返回時被調用。 

  • onDestroyView()    當與fragment關聯的視圖體系正被移除時被調用。 

  • onDetach()    當fragment正與activity解除關聯時被調用。

fragment的生命週期流程其實是受其宿主activity影響,如圖3所示。在這張圖中,能夠看到activity的每一個連續狀態是如何決定fragment可能接收到哪一個回調函數的。例如,當activity接收到它的onCreate()回調時,activity之中的fragment接收到的僅僅是onActivityCreated()回調。

一旦activity處於resumed狀態,則能夠在activity中自由的添加或者移除fragment。所以,只有當activity處於resumed狀態時,fragment的生命週期才能夠獨立變化。

然而,當activity離開恢復狀態時,fragment再一次被activity推入它的生命週期中

相關文章
相關標籤/搜索