Android入門教程 | Fragment 基礎概念

Android_anzi發表於2021-10-18

什麼是Fragment?

Fragment,直譯為“碎片”,“片段”。 Fragment 表示 FragmentActivity 中的行為或介面的一部分。可以在一個 Activity 中組合多個片段,從而構建多窗格介面,並在多個 Activity 中重複使用某個片段。可以將片段視為 Activity 的模組化組成部分,它具有自己的生命週期,能接收自己的輸入事件,並且可以在 Activity 執行時新增或移除片段(這有點像可以在不同 Activity 中重複使用的“子 Activity”)。

片段必須始終託管在 Activity 中,其生命週期直接受宿主 Activity 生命週期的影響。例如,當 Activity 暫停時,Activity 的所有片段也會暫停;當 Activity 被銷燬時,所有片段也會被銷燬。

不過,當 Activity 正在執行(處於已恢復生命週期狀態)時,可以獨立操縱每個片段,如新增或移除片段。當執行此類片段事務時,也可將其新增到由 Activity 管理的返回棧 — Activity 中的每個返回棧條目都是一條已發生片段事務的記錄。藉助返回棧,使用者可以透過按返回按鈕撤消片段事務(後退)。

Fragment的優點

  • Fragment載入靈活,替換方便。定製你的UI,在不同尺寸的螢幕上建立合適的UI,提高使用者體驗。
  • 可複用,頁面佈局可以使用多個Fragment,不同的控制元件和內容可以分佈在不同的Fragment上。
  • 使用Fragment,可以少用一些Activity。一個Activity可以管轄多個Fragment。

Fragment生命週期

Fragment 類的程式碼與 Activity 非常相似。它包含與 Activity 類似的回撥方法,如 onCreate()、onStart()、onPause() 和 onStop()。實際上,如果要將現有 Android 應用轉換為使用片段,可能只需將程式碼從 Activity 的回撥方法移入片段相應的回撥方法中。

通常,至少應實現以下生命週期方法

  • onCreate() 系統會在建立片段時呼叫此方法。當片段經歷暫停或停止狀態繼而恢復後,如果希望保留此片段的基本元件,則應在實現中將其初始化。
  • onCreateView() 系統會在片段首次繪製其介面時呼叫此方法。如要為片段繪製介面,從此方法中返回的 View 必須是片段佈局的根檢視。如果片段未提供介面,可以返回 null。
  • onPause() 系統會將此方法作為使用者離開片段的第一個訊號(但並不總是意味著此片段會被銷燬)進行呼叫。通常,應在此方法內確認在當前使用者會話結束後仍然有效的任何更改(因為使用者可能不會返回)。

可能還想擴充套件幾個子類,而非 Fragment 基類

  • DialogFragment 顯示浮動對話方塊。使用此類建立對話方塊可有效代替使用 Activity 類中的對話方塊輔助方法,因為您可以將片段對話方塊納入由 Activity 管理的片段返回棧,從而使使用者能夠返回清除的片段。
  • ListFragment 顯示由介面卡(如 SimpleCursorAdapter)管理的一系列專案,類似於 ListActivity。該類提供幾種管理列表檢視的方法,如用於處理點選事件的 onListItemClick() 回撥。(請注意,顯示列表的首選方法是使用 RecyclerView,而非 ListView。在此情況下,需在列表佈局中建立包含 RecyclerView 的片段。如需瞭解具體操作方法,請參閱使用 RecyclerView 建立列表)
  • PreferenceFragmentCompat 以列表形式顯示 Preference 物件的層次結構。此類用於為應用建立設定螢幕。
建立Fragment,使用自定義介面

片段通常用作 Activity 介面的一部分,並且會將其自己的佈局融入 Activity。

如要為片段提供佈局,必須實現  onCreateView() 回撥方法,Android 系統會在片段需要繪製其佈局時呼叫該方法。此方法的實現所返回的 View 必須是片段佈局的根檢視。

如要從 onCreateView() 返回佈局,可以透過 XML 中定義的佈局資源來擴充套件布局。為幫助您執行此操作,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 引數是片段佈局將插入到的父級 ViewGroup(來自 Activity 的佈局)。savedInstanceState 引數是在恢復片段時,提供上一片段例項相關資料的 Bundle(處理片段生命週期部分對恢復狀態做了詳細闡述)。

inflate() 方法帶有三個引數

  • 想要擴充套件的佈局的資源 ID。
  • 將作為擴充套件布局父項的 ViewGroup。傳遞 container 對系統向擴充套件布局的根檢視(由其所屬的父檢視指定)應用佈局引數具有重要意義。
  • 指示是否應在擴充套件期間將擴充套件布局附加至 ViewGroup(第二個引數)的布林值。(在本例中,此值為 false,因為系統已將擴充套件布局插入 container,而傳遞 true 值會在最終佈局中建立一個多餘的檢視組。)

接下來,需將該片段新增到您的 Activity 中。

向Activity新增Fragment

通常,片段會向宿主 Activity 貢獻一部分介面,作為 Activity 整體檢視層次結構的一部分嵌入到 Activity 中。可以透過兩種方式向 Activity 佈局新增片段(以下為程式碼片段,並非完整程式碼)。

靜態方式

在 Activity 的佈局檔案內宣告片段。 在本例中,您可以將片段當作檢視來為其指定佈局屬性。例如,以下是擁有兩個片段的 Activity 的佈局檔案:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns: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 佈局時,系統會將佈局中指定的每個片段例項化,併為每個片段呼叫  onCreateView() 方法,以檢索每個片段的佈局。系統會直接插入片段返回的 View,從而代替  <fragment> 元素。

注意:每個片段都需要唯一識別符號,重啟 Activity 時,系統可使用該識別符號來恢復片段(也可以使用該識別符號來捕獲片段,從而執行某些事務,如將其移除)。可以透過兩種方式為片段提供 ID:
為 android:id 屬性提供唯一 ID。
為 android:tag 屬性提供唯一字串。

Java程式碼載入Fragment

或者,透過程式設計方式將片段新增到某個現有 ViewGroup。 在 Activity 執行期間,您可以隨時將片段新增到 Activity 佈局中。您只需指定要將片段放入哪個 ViewGroup。

如要在 Activity 中執行片段事務(如新增、移除或替換片段),則必須使用 FragmentTransaction 中的 API。如下所示,可以從 FragmentActivity 獲取一個 FragmentTransaction 例項:

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

然後,可以使用  add() 方法新增一個片段,指定要新增的片段以及將其插入哪個檢視。例如:

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

傳遞到  add() 的第一個引數是 ViewGroup,即應放置片段的位置,由資源 ID 指定,第二個引數是要新增的片段。 一旦透過 FragmentTransaction 做出了更改,就必須呼叫  commit() 以使更改生效。

管理Fragment

如要管理 Activity 中的片段,需使用 FragmentManager。如要獲取它,請從 Activity 呼叫  getSupportFragmentManager()

可使用 FragmentManager 執行的操作包括

  • 透過  findFragmentById()(針對在 Activity 佈局中提供介面的片段)或 findFragmentByTag()(針對提供或不提供介面的片段)獲取 Activity 中存在的片段。
  • 透過  popBackStack()(模擬使用者發出的返回命令)使片段從返回棧中彈出。
  • 透過  addOnBackStackChangedListener() 註冊偵聽返回棧變化的偵聽器。

也可使用 FragmentManager 開啟一個 FragmentTransaction,透過它來執行某些事務,如新增和移除片段。

執行Fragment事務

在 Activity 中使用片段的一大優點是,可以透過片段執行新增、移除、替換以及其他操作,從而響應使用者互動。提交給 Activity 的每組更改均稱為事務,並且可使用 FragmentTransaction 中的 API 來執行一項事務。也可將每個事務儲存到由 Activity 管理的返回棧內,從而讓使用者能夠回退片段更改(類似於回退 Activity)。

如下所示,可以從 FragmentManager 獲取一個 FragmentTransaction 例項:

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

每個事務都是想要同時執行的一組更改。可以使用 add()、remove() 和 replace() 等方法,為給定事務設定您想要執行的所有更改。然後,如要將事務應用到 Activity,必須呼叫 commit()。

不過,在呼叫 commit() 之前,可能希望呼叫 addToBackStack(),以將事務新增到片段事務返回棧。該返回棧由 Activity 管理,允許使用者透過按返回按鈕返回上一片段狀態。

例如,以下示例說明如何將一個片段替換為另一個片段,以及如何在返回棧中保留先前的狀態:

// Create new fragment and transactionFragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stacktransaction.replace(R.id.fragment_container, newFragment);transaction.addToBackStack(null);
// Commit the transactiontransaction.commit();

在本例中,newFragment 會替換目前在 R.id.fragment_container ID 所標識的佈局容器中的任何片段(如有)。透過呼叫 addToBackStack(),可以將替換事務儲存到返回棧,以便使用者能夠透過按返回按鈕撤消事務並回退到上一片段。

然後,FragmentActivity 會自動透過 onBackPressed() 從返回棧檢索片段。

如果向事務新增多個更改(如又一個 add() 或 remove()),並呼叫 addToBackStack(),則呼叫 commit() 前應用的所有更改都將作為單一事務新增到返回棧,並且返回按鈕會將它們一併撤消。

向 FragmentTransaction 新增更改的順序無關緊要,不過:

必須最後呼叫 commit()。 如果要向同一容器新增多個片段,則新增片段的順序將決定它們在檢視層次結構中出現的順序。 如果沒有在執行刪除片段的事務時呼叫 addToBackStack(),則事務提交時該片段會被銷燬,使用者將無法回退到該片段。不過,如果在刪除片段時呼叫 addToBackStack(),則系統會停止該片段,並隨後在使用者回退時將其恢復。

呼叫 commit() 不會立即執行事務,而是在 Activity 的介面執行緒(“主”執行緒)可執行該操作時,再安排該事務線上程上執行。不過,如有必要,也可以從介面執行緒呼叫 executePendingTransactions(),以立即執行 commit() 提交的事務。通常不必這樣做,除非其他執行緒中的作業依賴該事務。

注意:只能在 Activity 儲存其狀態(當使用者離開 Activity)之前使用 commit() 提交事務。如果試圖在該時間點後提交,則會引發異常。這是因為如需恢復 Activity,則提交後的狀態可能會丟失。對於丟失提交無關緊要的情況,請使用  commitAllowingStateLoss()
生命週期變化

Fragment被建立的時候

它會經歷以下狀態

onAttach()onCreate()onCreateView()onActivityCreated()

Fragment 對使用者可見的時候

它會經歷以下狀態

onStart()onResume()

Fragment進入“後臺模式”的時候

它會經歷以下狀態

onPause()onStop()

Fragment被銷燬了(或者持有它的activity被銷燬了)

它會經歷以下狀態

onPause()onStop()onDestroyView()onDestroy()onDetach()

Fragment與Activity不同的生命週期

Fragment 的大部分狀態都和 Activity 很相似,但 fragment 有一些新的狀態。

Fragment不同於Activity的生命週期

  • onAttached() —— 當fragment被加入到activity時呼叫(在這個方法中可以獲得所在的activity)。
  • onCreateView() —— 當activity要得到fragment的layout時,呼叫此方法,fragment在其中建立自己的layout(介面)。
  • onActivityCreated() —— 當activity的onCreated()方法返回後呼叫此方法
  • onDestroyView() —— 當fragment中的檢視被移除的時候,呼叫這個方法。
  • onDetach() —— 當fragment和activity分離的時候,呼叫這個方法。

一旦activity進入resumed狀態(也就是running狀態),你就可以自由地新增和刪除fragment了。因此,只有當activity在resumed狀態時,fragment的生命週期才能獨立的運轉,其它時候是依賴於activity的生命週期變化的。

處理Fragment生命週期

管理片段生命週期與管理 Activity 生命週期很相似。和 Activity 一樣,片段也以三種狀態存在:

  • 已恢復:片段在執行中的 Activity 中可見。
  • 已暫停:另一個 Activity 位於前臺並具有焦點,但此片段所在的 Activity 仍然可見(前臺 Activity 部分透明,或未覆蓋整個螢幕)。
  • 已停止:片段不可見。宿主 Activity 已停止,或片段已從 Activity 中移除,但已新增到返回棧。已停止的片段仍處於活動狀態(系統會保留所有狀態和成員資訊)。不過,它對使用者不再可見,並隨 Activity 的終止而終止。 與 Activity 一樣,您也可使用 onSaveInstanceState(Bundle)、ViewModel 和持久化本地儲存的組合,在配置變更和程式終止後保留片段的介面狀態。如要了解保留介面狀態的更多資訊,請參閱儲存介面狀態。

對於 Activity 生命週期與片段生命週期而言,二者最顯著的差異是在其各自返回棧中的儲存方式。預設情況下,Activity 停止時會被放入由系統管理的 Activity 返回棧中。不過,只有在移除片段的事務執行期間透過呼叫 addToBackStack() 顯式請求儲存例項時,系統才會將片段放入由宿主 Activity 管理的返回棧。

在其他方面,管理片段生命週期與管理 Activity 生命週期非常相似;對此,可採取相同的做法。

注意:如果 Fragment 中需要 Context 物件,則可以呼叫 getContext()。但請注意,只有在該片段附加到 Activity 時才需呼叫 getContext()。如果尚未附加該片段,或者其在生命週期結束期間已分離,則 getContext() 返回 null。

Fragment相關面試題:

1. 如何切換 fragement(不重新例項化)

翻看了 Android 官方 Doc,和一些元件的原始碼,發現 replace()這個方法只是在上一個 Fragment不再需要時採用的簡便方法.

正確的切換方式是 add(),切換時 hide(),add()另一個 Fragment;再次切換時,只需 hide()當前,show()另一個。這樣就能做到多個 Fragment 切換不重新例項化:

2. Fragment 的的優點

  • Fragment 可以使你能夠將 activity 分離成多個可重用的元件,每個都有它自己的生命週期和UI。
  • Fragment 可以輕鬆得建立動態靈活的 UI 設計,可以適應於不同的螢幕尺寸。從手機到平板電腦。
  • Fragment 是一個獨立的模組,緊緊地與 activity 繫結在一起。可以執行中動態地移除、加入、交換等。
  • Fragment 提供一個新的方式讓你在不同的安卓裝置上統一你的 UI。
  • Fragment 解決 Activity 間的切換不流暢,輕量切換。
  • Fragment 替代 TabActivity 做導航,效能更好。
  • Fragment 在 4.2.版本中新增巢狀 fragment 使用方法,能夠生成更好的介面效果。

3. Fragment 如何實現類似 Activity 棧的壓棧和出棧效果

Fragment 的事物管理器內部維持了一個雙向連結串列結構,該結構可以記錄我們每次 add 的Fragment 和 replace 的 Fragment,然後當我們點選 back 按鈕的時候會自動幫我們實現退棧操作。

4. Fragment 的 replace 和 add 方法的區別

Fragment 本身並沒有 replace 和 add 方法,這裡的理解應該為使用 FragmentManager 的 replace和 add 兩種方法切換 Fragment 時有什麼不同。

我們經常使用的一個架構就是透過 RadioGroup 切換 Fragment,每個 Fragment 就是一個功能模組。

Fragment 的容器一個 FrameLayout,add 的時候是把所有的 Fragment 一層一層的疊加到了FrameLayout 上了,而 replace 的話首先將該容器中的其他 Fragment 去除掉然後將當前 Fragment新增到容器中。

一個 Fragment 容器中只能新增一個 Fragment 種類,如果多次新增則會報異常,導致程式終止,而 replace 則無所謂,隨便切換。

因為透過 add 的方法新增的 Fragment,每個 Fragment 只能新增一次,因此如果要想達到切換效果需要透過 Fragment 的的 hide 和 show 方法結合者使用。將要顯示的 show 出來,將其他 hide起來。這個過程 Fragment 的生命週期沒有變化。透過 replace 切換 Fragment,每次都會執行上一個 Fragment 的 onDestroyView,新 Fragment的 onCreateView、onStart、onResume 方法。

基於以上不同的特點我們在使用的使用一定要結合著生命週期操作我們的檢視和資料。

5. Fragment與Activity之間是如何傳值的

  • Activity向Fragment傳值:

    將要傳的值,放到bundle物件裡; 在Activity中建立該Fragment的物件fragment,
    透過呼叫 fragment.setArguments()傳遞到fragment中;
    在該Fragment中透過呼叫getArguments()得到bundle物件,就能得到裡面的值。
  • Fragment向Activity傳值:

    在Activity中呼叫getFragmentManager()得到fragmentManager,,呼叫findFragmentByTag(tag)或者透過findFragmentById(id)
    FragmentManager fragmentManager = getFragmentManager();
    Fragment fragment = fragmentManager.findFragmentByTag(tag);

透過回撥的方式,定義一個介面(可以在 Fragment 類中定義),介面中有一個空的方法,在 fragment 中需要的時候呼叫介面的方法,值可以作為引數放在這個方法中,然後讓 Activity 實現這個介面,必然會重寫這個方法,這樣值就傳到了 Activity 中。

6. Fragment生命週期

  • onAttach(Contextcontext):在 Fragment 和 Activity 關聯上的時候呼叫,且僅呼叫一次。在該回撥中我們可以將 context 轉化為 Activity 儲存下來,從而避免後期頻繁呼叫getAtivity() 獲取 Activity 的局面,避免了在某些情況下 getAtivity() 為空的異常(Activity和 Fragment 分離的情況下)。同時也可以在該回撥中將傳入的Arguments提取並解析,在這裡強烈推薦透過setArguments給Fragment傳引數,因為在應用被系統回收時Fragment不會儲存相關屬性。
  • onCreate:在最初建立Fragment的時候會呼叫,和Activity的onCreate類似。
  • View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState):在準備繪製Fragment介面時呼叫,返回值為Fragment要繪製佈局的根檢視,當然也可以返回null。注意使用inflater構建View時一定要將attachToRoot指明false,因為Fragment會自動將檢視新增到container中,attachToRoot為true會重複新增報錯。onCreateView並不是一定會被呼叫,當新增的是沒有介面的Fragment就不會呼叫,比如呼叫FragmentTransaction的add(Fragment fragment, String tag)方法。
  • onActivityCreated :在 Activity 的 onCreated 執行完時會呼叫。
  • onStart() :Fragment對使用者可見的時候呼叫,前提是 Activity 已經 started。
  • onResume():Fragment和使用者之前可互動時會呼叫,前提是Activity已經resumed。
  • onPause():Fragment和使用者之前不可互動時會呼叫。
  • onStop():Fragment不可見時會呼叫。
  • onDestroyView():在移除Fragment相關檢視層級時呼叫。
  • onDestroy():最終清楚Fragment狀態時會呼叫。
  • onDetach():Fragment和Activity解除關聯時呼叫。

7. ViewPager對Fragment生命週期的影響

ViewPager+Fragment 是比較常見的組合了,一般搭配ViewPager的FragmentPagerAdapter 或 FragmentStatePagerAdapter 使用。不過 ViewPager 為了防止滑動出現卡頓,有一個快取機制,預設情況下 ViewPager 會建立並快取當前頁面左右兩邊的頁面(如Fragment)。此時左右兩個 Fragment 都會執行從 onAttach->….->onResume 的生命週期,明明 Fragment 沒有顯示卻已經到onResume 了,在某些情況下會出現問題。比如資料的載入時機、判斷 Fragment 是否可見等。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2837921/,如需轉載,請註明出處,否則將追究法律責任。

相關文章