Android 頁面 Fragment(官網翻譯)

一個Fragment看起來就是一個和Activity一樣的用戶界面。你可以結合多個Fragments到一個activity中,來構建一個有多方面功能的UI,還可以重用同一個Fragment在多個activities中。你可以把它當成是activity的一個組件,每個Fragment有單獨的生命周期,你可以在activity運行時進行進行添加和移除Fragment。相比較于activity,Fragment更加輕量級,更加靈活。

一個Fragment總是被植入在一個activity中,并且其生命周期受其父activity直接影響,比如activity處于暫停,則其中的Fragment都暫停,activity銷毀,則所有Fragment都銷毀。然而,當一個activity運行時,你可以獨立的操作每一個Fragment,比如添加和刪除他們。當你進行類似的操作時,你可以將Fragment添加入被activity管理的后退棧中,這樣用戶可以通過點擊返回按鈕來返回之前打開的Fragment。

你可以將一個Fragment作為activity的一部分添加到其布局文件中,通過聲明<fragment>元素作為ViewGroup的一部分。當然,這不是必須的,你也可以將Fragment作為一個沒有自己的UI的不可見的activity的工人。

設計原理

Android在Android3.0(API 11)上介紹了fragmens,主要是支持在大屏幕上更動態和靈活的UI設計,比如平板。

平板上兩個模塊定義在一個activity中,而手機是分離的

創建一個Fragment

fragment的生命周期

為了創建一個Fragment,你必須創建一個Fragment的子類,Fragment編碼就像是一個Activity。包含一些相同的回調函數,比如onCreate(),onStart(),onPause(),和onStop()。事實上,如果你覆蓋一個已有的Android應用來使用Fragment,你只用簡單的移動對應的代碼到對應的回調函數中。

通常情況下,你應該至少實現以下的生命周期函數:
onCreate()
系統在創建fragment時調用。
你應該在這里初始化你想要在暫停或者停止恢復時保持的fragment部分。
onCreateView()
系統在fragment第一次構造用戶界面時調用。
為了給你的fragment構造一個UI,你一定要記得return一個View,這個View是來自你的fragment的根布局,你也可以return null,如果這個fragment沒有UI。
onPause()
系統會在用戶產生離開這個fragment的跡象時調用。
這里通常是你提交變化的地方。因為用戶或許不會回來。

大多數的應用都應該為至少每一個fragment實現這三個方法,但還有其他的回調函數,你也應該用他們來處理fragment的各個階段。

這里還有一些fragment的子類:
DialogFragment
顯示一個懸浮的對話框。好處是你能將其加入后退棧中,允許用戶返回。
ListFragment
顯示一個通過adapter管理的列表,類似ListActivity,它提供許多方法管理一個列表view。
PreferenceFragment
在一個列表中顯示一個分級Preference對象,類似PreferenceActivity。當你為你的應用創建“設置”activity時很有用。

添加用戶界面

一個fragment常作為activity用戶界面的一部分。
要使用一個fragment的布局文件,你必須實現onCreateView()回調函數,return根布局。

如果你的fragment是ListFragment的子類,它會在onCreateView()中自動返回一個ListView。你就不用去實現了。

為了返回一個布局,可以從一個定義在xml中的布局資源中載入來提供幫助。onCreateView()提供一個LayoutInflater對象

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // 載入布局資源
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

container參數是存放fragment的布局的ViewGroup對象,savedInstanceState參數是一個Bundle,跟activity的onCreate()中Bundle差不多,用于狀態恢復。但是fragment的onCreate()中也有Bundle參數,所以此處的Bundle中存放的數據與onCreate()中存放的數據還是不同的。至于詳細信息,請參考“操控fragment的生命周期“。

Inflate()方法有三個參數:
1.layout的資源ID。
2.存放fragment的layout的ViewGroup。
3.布爾型數據表示是否在創建fragment的layout期間,把layout附加到container上(在這個例子中, 指定了false, 因為系統已經把展開的layout插入到container –傳入true會在最后的layout中創建一個多余的view group.)

將fragment添加到activity

一般情況下,fragment把它的layout作為activitiy的layout的一部分合并到activity中,有兩種方法將一個fragment添加到activity中:

  • 在activity的layoutxml文件中聲明fragment:

在這種情況下,你可以像為View一樣, 為fragment指定layout屬性.例子是一個有兩個fragment的activity的layout:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <fragmentandroid:name="com.example.news.ArticleListFragment"
           android:id="@+id/list"
           android:layout_weight="1"
           android:layout_width="0dp"
          android:layout_height="match_parent"/>
   <fragmentandroid:name="com.example.news.ArticleReaderFragment"
           android:id="@+id/viewer"
           android:layout_weight="2"
           android:layout_width="0dp"
          android:layout_height="match_parent"/>
</LinearLayout
  • 在代碼中添加fragment到一個ViewGroup:
    <fragment>中的 android:name屬性指定了在layout中實例化的Fragment類. 當系統創建這個activity layout時,它實例化每一個在layout中指定的fragment,并調用每一個上的onCreateView()方法,來獲取每一個 fragment的layout.系統將從fragment返回的View直接插入到<fragment>元素所在的地方.

每一個fragment都需要一個唯一的標識,如果activity重啟,系統可以用來恢復fragment(并且你也可以用來捕獲fragment來處理事務,例如移除它.)
有3種方法來為一個fragment提供一個標識:

  • 為 android:id 屬性提供一個唯一ID.

  • 為 android:tag 屬性提供一個唯一字符串.

  • 如果以上2個你都沒有提供, 系統使用容器view的ID.

  • 或者撰寫代碼將fragment添加到一個已存在的ViewGroup.
    當activity運行的任何時候, 都可以將fragment添加到activity layout.只需簡單的指定一個需要放置fragment的ViewGroup.為了在你的 activity中操作fragment事務(例如添加,移除,或代替一個fragment),必須使用來自FragmentTransaction 的API.

可以按如下方法,從你的Activity取得一個 FragmentTransaction 的實例:

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

然后你可以使用 add() 方法添加一個fragment, 指定要添加的fragment和要插入的view.

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

add()的第一個參數是fragment要放入的ViewGroup, 由resource ID指定,第二個參數是需要添加的fragment.一旦用FragmentTransaction做了改變,為了使改變生效,必須調用commit().

添加一個無UI的fragment

之前的例子展示了對UI的支持, 如何將一個fragment添加到activity.然而,也可以使用fragment來為activity提供后臺行為而不用展現額外的UI.
要添加一個無UI的fragment, 需要從activity使用add(Fragment, String)來添加fragment (為fragment提供一個唯一的字符串"tag", 而不是一個view ID).這么做添加了fragment,但因為它沒有關聯到一個activity layout中的一個view, 所以不會接收到onCreateView()調用.因此不必實現此方法.
為fragment提供一個字符串tag并不是專門針對無UI的fragment的–也可以提供字符串tag給有UI的fragment–但是如果fragment沒有UI,那么這個tag是僅有的標識它的途徑.如果隨后你想從activity獲取這個fragment, 需要使用findFragmentByTag().

管理Fragment

要在activity中管理fragment,需要使用FragmentManager. 通過調用activity的getFragmentManager()取得它的實例.
可以通過FragmentManager做一些事情, 包括:

  • 使用findFragmentById()(用于在activity layout中提供一個UI的fragment)或findFragmentByTag()(適用于有或沒有UI的fragment)獲取activity中存在的fragment
  • 將fragment從后臺堆棧中彈出, 使用 popBackStack() (模擬用戶按下BACK 命令).
  • 使用addOnBackStackChangeListener()注冊一個監聽后臺堆棧變化的listener.

處理Fragment事務

關于在activity中使用fragment的很強的一個特性是:根據用戶的交互情況,對fragment進行添加,移除,替換,以及執行其他動作.提交給activity的每一套變化被稱為一個事務,可以使用在FragmentTransaction中的 API 處理.我們也可以保存每一個事務到一個activity管理的backstack,允許用戶經由fragment的變化往回導航(類似于通過 activity往后導航).
從 FragmentManager 獲得一個FragmentTransaction實例 :

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

每一個事務都是同時要執行的一套變化.可以在一個給定的事務中設置你想執行的所有變化,使用諸如 add()、remove()和 replace().然后, 要給activity應用事務, 必須調用 commit().

在調用commit()之前, 你可能想調用 addToBackStack(),將事務添加到一個fragment事務的backstack. 這個back stack由activity管理, 并允許用戶通過按下 BACK按鍵返回到前一個fragment狀態.

舉個例子, 這里是如何將一個fragment替換為另一個, 并在后臺堆棧中保留之前的狀態:

// Create new fragment and transaction  
Fragment newFragment = newExampleFragment();  
FragmentTransaction transaction =getFragmentManager().beginTransaction();  
// Replace whatever is in thefragment_container view with this fragment,  
// and add the transaction to the backstack  
transaction.replace(R.id.fragment_container,newFragment);  
transaction.addToBackStack(null);  
// Commit the transaction  
transaction.commit();  

在這個例子中,newFragment替換了當前layout容器中的由R.id.fragment_container標識的fragment.通過調用 addToBackStack(), replace事務被保存到back stack,因此用戶可以回退事務,并通過按下BACK按鍵帶回前一個fragment.
如果添加多個變化到事務(例如add()或remove())并調用addToBackStack(),然后在你調用commit()之前的所有應用的變化會被作為一個單個事務添加到后臺堆棧, BACK按鍵會將它們一起回退.
添加變化到 FragmentTransaction的順序不重要, 除以下例外:

  • 必須最后調用 commit().
  • 如果添加多個fragment到同一個容器, 那么添加的順序決定了它們在view hierarchy中顯示的順序.

當執行一個移除fragment的事務時, 如果沒有調用 addToBackStack(), 那么當事務提交后,那個fragment會被銷毀,并且用戶不能導航回到它. 有鑒于此, 當移除一個fragment時,如果調用了addToBackStack(), 那么fragment會被停止, 如果用戶導航回來,它將會被恢復.

對于每一個fragment事務, 你可以應用一個事務動畫,通過在提交事務之前調用setTransition()實現.

調用 commit() 并不立即執行事務.恰恰相反, 它將事務安排排期, 一旦準備好,就在activity的UI線程上運行(主線程).如果有必要, 無論如何, 你可以從你的UI線程調用executePendingTransactions()來立即執行由commit()提交的事務. 但這么做通常不必要,除非事務是其他線程中的任務的一個從屬.

警告:你只能在activity保存它的狀態(當用戶離開activity)之前使用commit()提交事務.

與Activity通信

盡管Fragment被實現為一個獨立于Activity的對象,并且可以在多個activity中使用,但一個給定的fragment實例是直接綁定到包含它的activity的. 特別的,fragment可以使用 getActivity() 訪問Activity實例, 并且容易地執行比如在activity layout中查找一個view的任務.

View listView =getActivity().findViewById(R.id.list);
<span style="font-family:System;"> </span>  

同樣地,activity可以通過從FragmentManager獲得一個到Fragment的引用來調用fragment中的方法, 使用findFragmentById() 或 findFragmentByTag().

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

為Activity創建事件回調方法

在一些情況下, 你可能需要一個fragment與activity分享事件. 一個好的方法是在fragment中定義一個回調的interface, 并要求宿主activity實現它.當activity通過interface接收到一個回調, 必要時它可以和在layout中的其他fragment分享信息.
例如, 如果一個新的應用在activity中有2個fragment – 一個用來顯示文章列表(framgent A), 另一個顯示文章內容(fragment B) – 然后 framgent A必須告訴activity何時一個list item被選中,然后它可以告訴fragmentB去顯示文章.
在這個例子中, OnArticleSelectedListener 接口在fragment A中聲明:

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

如果activity沒有實現接口, fragment會拋出 ClassCastException 異常. 正常情形下,mListener成員會保持一個到activity的OnArticleSelectedListener實現的引用, 因此fragment A可以通過調用在OnArticleSelectedListener接口中定義的方法分享事件給activity.例如, 如果fragment A是一個 ListFragment的子類, 每次用戶點擊一個列表項, 系統調用在fragment中的onListItemClick(),然后后者調用 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 獲取文章.

添加項目到ActionBar

你的fragment可以通過實現 onCreateOptionMenu() 提供菜單項給activity的選項菜單(以此類推, Action Bar也一樣).為了使這個方法接收調用,無論如何, 你必須在 onCreate() 期間調用 setHasOptionsMenu() 來指出fragment愿意添加item到選項菜單(否則, fragment將接收不到對 onCreateOptionsMenu()的調用).

隨后從fragment添加到Option菜單的任何項,都會被追加到現有菜單項的后面.當一個菜單項被選擇, fragment也會接收到 對 onOptionsItemSelected() 的回調.也可以在你的fragment layout中通過調用registerForContextMenu() 注冊一個view來提供一個環境菜單.當用戶打開環境菜單, fragment接收到一個對 onCreateContextMenu() 的調用.當用戶選擇一個項目, fragment接收到一個對onContextItemSelected() 的調用.

注意: 盡管你的fragment會接收到它所添加的每一個菜單項被選擇后的回調, 但實際上當用戶選擇一個菜單項時, activity會首先接收到對應的回調.如果activity的on-item-selected回調函數實現并沒有處理被選中的項目, 然后事件才會被傳遞到fragment的回調.

這個規則適用于選項菜單和環境菜單.

處理fragment的生命周期

管理fragment的生命周期, 大多數地方和管理activity生命周期很像.和activity一樣, fragment可以處于3種狀態:

Resumed
在運行中的activity中fragment可見.

Paused
另一個activity處于前臺并擁有焦點, 但是這個fragment所在的activity仍然可見(前臺activity局部透明或者沒有覆蓋整個屏幕).

Stopped
要么是宿主activity已經被停止, 要么是fragment從activity被移除但被添加到后臺堆棧中.
停止狀態的fragment仍然活著(所有狀態和成員信息被系統保持著). 然而, 它對用戶不再可見, 并且如果activity被干掉,他也會被干掉.

其對應關系圖如下:


activity_fragment_lifecycle

和activity一樣, 你可以使用Bundle保持fragment的狀態, 萬一activity的進程被干掉,并且當activity被重新創建的時候, 你需要恢復fragment的狀態時就可以用到. 你可以在fragment的 onSaveInstanceState() 期間保存狀態, 并可以在 onCreate(), onCreateView() 或 onActivityCreated() 期間恢復它.

生命周期方面activity和fragment之間最重要的區別是各自如何在它的后臺堆棧中儲存. 在默認情況下, activity在停止后, 它會被放到一個由系統管理的用于保存activity的后臺堆棧.(因此用戶可以使用BACK按鍵導航回退到它).

然而, 僅當你在一個事務期間移除fragment時,顯式調用addToBackStack()請求保存實例時,才被放到一個由宿主activity管理的后臺堆棧. 另外, 管理fragment的生命周期和管理activity生命周期非常類似.因此, "managing the activitylifecycle"中的相同實踐也同樣適用于fragment. 你需要理解的是, activity的生命如何影響fragment的生命.

與activity生命周期的協調工作

fragment所生存的activity的生命周期,直接影響fragment的生命周期,每一個activity的生命周期的回調行為都會引起每一個fragment中類似的回調.
例如,當activity接收到onPause()時,activity中的每一個fragment都會接收到onPause().
Fragment 有一些額外的生命周期回調方法, 那些是處理與activity的唯一的交互,為了執行例如創建和銷毀fragment的UI的動作. 這些額外的回調方法是:

  • onAttach()
    當fragment被綁定到activity時被調用(Activity會被傳入.).
  • onCreateView()
    創建和fragment關聯的view hierarchy時調用.
  • onActivityCreated()
    當activity的onCreate()方法返回時被調用.
  • onDestroyView()
    當和fragment關聯的view hierarchy正在被移除時調用.
  • onDetach()
    當fragment從activity解除關聯時被調用.

fragment生命周期的流程, 以及宿主activity對它的影響,在圖3中顯示.在這個圖中,可以看到activity依次的每個狀態是如何決定fragment可能接收到的回調方法.例如, 當activity接收到它的onCreate(),activity中的fragment接收到最多是onActivityCreated().
一旦activity到達了resumed狀態, 你可以自由地在activity添加和移除fragment.因此,僅當activity處于resumed狀態時, fragment的生命周期才可以獨立變化.
無論如何, 當activity離開resumed狀態,fragment再次被activity的推入它自己的生命周期過程.

以上部分參考傲慢的上校的博客

例子

(未完待續)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Fragment要點 1、Fragment作為Activity界面的一部分組成出現 2、可以在一個Activity...
    玉圣閱讀 1,250評論 0 16
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,243評論 25 708
  • Fragment概述 Fragment是Activity中用戶界面的一個行為或者說是一部分。主要是支持大屏幕上動態...
    wangling90閱讀 11,586評論 5 75
  • 小轎車在茫茫的霾中穿過 車輪在積水的柏油路 濺起一溜白霧 蝸牛般奔馳 對面都是陌生人 一米二的距離 我看不清你的臉...
    老雷閱讀 128評論 0 1
  • 此刻很疲倦,和好友有一搭沒一搭地發著語音消息。 感情之路,走得真不順暢。 將我最隱私的真實一面展現在我最喜歡的人面...
    君曉墨閱讀 196評論 0 0