Fragment的理解, Fragment的生命周期

Fragment是什么

說了半天的Fragment,也看到這么多次Fragment這個名詞出現(xiàn),那么Fragment到底是什么東東呢?定義又是如何?

Fragment也可以叫為“片段”,但我覺得“片段”中文叫法有點生硬,還是保持叫Fragment比較好,它可以表示Activity中的行為或用戶界面部分。我們可以在一個Activity中用多個Fragment組合來構(gòu)建多窗格的UI,以及在多個Activity中重復使用某個Fragment。它有自己的生命周期,能接受自己的輸入,并且可以在 Activity 運行時添加或刪除Fragment(有點像在不同 Activity 中重復使用的“子 Activity”)。

簡單來說,F(xiàn)ragment其實可以理解為一個具有自己生命周期的控件,只不過這個控件又有點特殊,它有自己的處理輸入事件的能力,有自己的生命周期,又必須依賴于Activity,能互相通信和托管。

Fragment生命周期

如圖:

這張圖是Fragment生命周期和Activity生命周期對比圖,可以看到兩者還是有很多相似的地方,比如都有onCreate(),onStart(),onPause(),onDestroy()等等,因為Fragment是被托管到Activity中的,所以多了兩個onAttach()和onDetach()。這里講講與Activity生命周期不一樣的方法。

onAttach()

Fragment和Activity建立關(guān)聯(lián)的時候調(diào)用,被附加到Activity中去。

onCreate()

系統(tǒng)會在創(chuàng)建Fragment時調(diào)用此方法。可以初始化一段資源文件等等。

onCreateView()

系統(tǒng)會在Fragment首次繪制其用戶界面時調(diào)用此方法。 要想為Fragment繪制 UI,從該方法中返回的 View 必須是Fragment布局的根視圖。如果Fragment未提供 UI,您可以返回 null。

onViewCreated()

在Fragment被繪制后,調(diào)用此方法,可以初始化控件資源。

onActivityCreated()

當onCreate(),onCreateView(),onViewCreated()方法執(zhí)行完后調(diào)用,也就是Activity被渲染繪制出來后。

onPause()

系統(tǒng)將此方法作為用戶離開Fragment的第一個信號(但并不總是意味著此Fragment會被銷毀)進行調(diào)用。 通常可以在此方法內(nèi)確認在當前用戶會話結(jié)束后仍然有效的任何更改(因為用戶可能不會返回)。

onDestroyView()

Fragment中的布局被移除時調(diào)用。

onDetach()

Fragment和Activity解除關(guān)聯(lián)的時候調(diào)用。

但需要注一點是:除了onCreateView,其他的所有方法如果你重寫了,必須調(diào)用父類對于該方法的實現(xiàn)。

還有一般在啟動Fragment的時候,它的生命周期就會執(zhí)行這幾個方法。

Fragment怎么用

前面介紹了半天,不耐煩的人會說,這么多廢話,也不見的到底是如何使用,畢竟我們是開發(fā)者,需要的使用方式,那么現(xiàn)在就來說說用法如何吧。兩種方式:靜態(tài)用法和動態(tài)用法。

靜態(tài)用法

1、繼承Fragment,重寫onCreateView決定Fragemnt的布局

2、在Activity中聲明此Fragment,就當和普通的View一樣

首先是布局文件:fragment1.xml

可以看到,這個布局文件非常簡單,只有一個LinearLayout,里面加入了一個TextView。我們再新建一個fragment2.xml :

然后新建一個類Fragment1,這個類是繼承自Fragment的:

publicclassFragment1extendsFragment{@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState) {returninflater.inflate(R.layout.fragment1, container,false); ? ? ?} ?}

可以看到,在onCreateView()方法中加載了fragment1.xml的布局。同樣fragment2.xml也是一樣的做法,新建一個Fragment2類:

publicclassFragment2extendsFragment{@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState) {returninflater.inflate(R.layout.fragment2, container,false); ? ? ?} ?}

然后打開或新建activity_main.xml作為主Activity的布局文件,在里面加入兩個Fragment的引用,使用android:name前綴來引用具體的Fragment:

最后新建MainActivity作為程序的主Activity,里面的代碼非常簡單,都是自動生成的:

publicclassMainActivityextendsActivity{@Overrideprotectedvoid onCreate(BundlesavedInstanceState) {super.onCreate(savedInstanceState); ? ? ? ? ?setContentView(R.layout.activity_main); ? ? ?} ?}

現(xiàn)在我們來運行一次程序,就會看到,一個Activity很融洽地包含了兩個Fragment,這兩個Fragment平分了整個屏幕,效果圖如下:

動態(tài)用法

上面僅僅是Fragment簡單用法,它真正強大部分是在動態(tài)地添加到Activity中,那么動態(tài)用法又是如何呢?

還是在靜態(tài)用法代碼的基礎(chǔ)上修改,打開activity_main.xml,將其中對Fragment的引用都刪除,只保留最外層的LinearLayout,并給它添加一個id,因為我們要動態(tài)添加Fragment,不用在XML里添加了,刪除后代碼如下:

然后打開MainActivity,修改其中的代碼如下所示:

publicclassMainActivityextendsActivity{@Overrideprotectedvoid onCreate(BundlesavedInstanceState) {super.onCreate(savedInstanceState); ? ? ? ? ?setContentView(R.layout.activity_main);Displaydisplay = getWindowManager().getDefaultDisplay();if(display.getWidth() > display.getHeight()) {Fragment1fragment1 =newFragment1(); ? ? ? ? ? ? ?getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit(); ? ? ? ? ?}else{Fragment2fragment2 =newFragment2(); ? ? ? ? ? ? ?getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit(); ? ? ? ? ?} ? ? ?} ? ?}

看到了沒,首先,我們要獲取屏幕的寬度和高度,然后進行判斷,如果屏幕寬度大于高度就添加fragment1,如果高度大于寬度就添加fragment2。動態(tài)添加Fragment主要分為4步:

1.獲取到FragmentManager,在Activity中可以直接通過getFragmentManager得到。

2.開啟一個事務,通過調(diào)用beginTransaction方法開啟。

3.向容器內(nèi)加入Fragment,一般使用replace方法實現(xiàn),需要傳入容器的id和Fragment的實例。

4.提交事務,調(diào)用commit方法提交。

現(xiàn)在運行一下程序,效果如下圖所示:

要想管理 Activity 中的片段,需要使用 FragmentManager。要想獲取它,需要 Activity 調(diào)用 getFragmentManager()。

使用 FragmentManager 執(zhí)行的操作包括:

通過 findFragmentById()(對于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(對于提供或不提供 UI 的片段)獲取 Activity 中存在的片段

通過 popBackStack()將片段從返回棧中彈出

通過 addOnBackStackChangedListener() 注冊一個偵聽返回棧變化的偵聽器

也可以使用 FragmentManager 打開一個 FragmentTransaction,通過它來執(zhí)行某些事務,如添加和刪除片段。

Fragment通信

盡管 Fragment 是作為獨立于 Activity的對象實現(xiàn),并且可在多個 Activity 內(nèi)使用,但Fragment 的給定實例會直接綁定到包含它的 Activity。具體地說,F(xiàn)ragment 可以通過 getActivity() 訪問 Activity實例,并輕松地執(zhí)行在 Activity 布局中查找視圖等任務。如:

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

同樣地,Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通過從 FragmentManager 獲取對 Fragment 的引用來調(diào)用Fragment中的方法。例如:

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

創(chuàng)建對 Activity 的事件回調(diào)

在某些情況下,可能需要通過與 Activity 共享事件。執(zhí)行此操作的一個好方法是,在Fragment 內(nèi)定義一個回調(diào)接口,并要求宿主 Activity 實現(xiàn)它。 當 Activity 通過該接口收到回調(diào)時,可以根據(jù)需要與布局中的其他Fragment共享這些信息。

例如,如果一個新聞應用的 Activity 有兩個Fragment ,一個用于顯示文章列表(Fragment A),另一個用于顯示文章(Fragment B)—,那么Fragment A必須在列表項被選定后告知 Activity,以便它告知Fragment B 顯示該文章。 在本例中,OnArticleSelectedListener 接口在片段 A 內(nèi)聲明:

public staticclassFragmentAextendsListFragment{ ? ? public interfaceOnArticleSelectedListener{ ? ? ? ? public void onArticleSelected(UriarticleUri); ? ?} }

然后,該Fragment的宿主 Activity 會實現(xiàn) OnArticleSelectedListener 接口并替代 onArticleSelected(),將來自Fragment A 的事件通知Fragment B。為確保宿主 Activity 實現(xiàn)此界面,F(xiàn)ragment A 的 onAttach() 回調(diào)方法(系統(tǒng)在向 Activity 添加Fragment時調(diào)用的方法)會通過轉(zhuǎn)換傳遞到 onAttach() 中的 Activity 來實例化 OnArticleSelectedListener 的實例:

public staticclassFragmentAextendsListFragment{OnArticleSelectedListenermListener;@Overridepublic void onAttach(Activityactivity) {super.onAttach(activity);try{ ? ? ? ? ? ? mListener = (OnArticleSelectedListener) activity; ? ? ? ?}catch(ClassCastExceptione) {thrownewClassCastException(activity.toString() +" must implement OnArticleSelectedListener"); ? ? ? ?} ? ? } }

如果 Activity 未實現(xiàn)界面,則片段會引發(fā) ClassCastException。實現(xiàn)時,mListener 成員會保留對 Activity 的 OnArticleSelectedListener 實現(xiàn)的引用,以便Fragment A 可以通過調(diào)用 OnArticleSelectedListener 界面定義的方法與 Activity 共享事件。例如,如果Fragment A 是 ListFragment 的一個擴展,則用戶每次點擊列表項時,系統(tǒng)都會調(diào)用Fragment中的 onListItemClick(),然后該方法會調(diào)用 onArticleSelected() 以與 Activity 共享事件:

public staticclassFragmentAextendsListFragment{OnArticleSelectedListenermListener;@Overridepublic void onListItemClick(ListViewl,Viewv, int position, long id) {UrinoteUri =ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id); ? ? ? ?mListener.onArticleSelected(noteUri); ? ?}}

Fragment是否很完美

因為Fragment是由FragmentManager來管理,每一個Activity有一個FragmentManager,管理著一個Fragment的棧,Activity是系統(tǒng)級別的,由系統(tǒng)來管理ActivityManager,棧也是系統(tǒng)范圍的。而Fragment則是每個Activity范圍內(nèi)的,所以在使用Fragment的時候也有幾點要注意。

同一個Activity中,只能有一個ID或TAG標識的Fragment實例。

這很容易理解,同一個范圍內(nèi),有標識的實例肯定是要唯一才行(否則還要標識干嘛)這個在布局中經(jīng)常犯錯,在布局中寫Fragment最好不要加ID或者TAG,否則很容易出現(xiàn)不允許創(chuàng)建的錯誤。我的原則是如果放在布局中,就不要加ID和TAG,如果需要ID和TAG就全用代碼控制。創(chuàng)建新實例前先到FragmentManager中查找一番,這也正是有標識的意義所在。

一個Activity中有一個Fragment池,實例不一定會被銷毀,可能會保存在池中。

這個跟第一點差不多。就好比系統(tǒng)會緩存Activity的實例一樣,F(xiàn)ragmentManager也會緩存Fragment實例,以方便和加速再次顯示。

FragmentManager的作用范圍是整個Activity,所以,某一個布局ID,不能重復被Fragment替換。

通常顯示Fragment有二種方式,一種是層疊到某個布局上,或者把某個布局上面的Fragment替換掉,但是這個布局不能出現(xiàn)二次,比如布局A中有ID為id的區(qū)域,要顯示為Fragment,此布局A,只能在一個Activity中顯示一個,否則第二個id區(qū)域不能被Fragment成功替換。因為雖有二個ID布局的實例,但ID是相同的,對FragmentManager來說是一樣的,它會認為只有一個,因為它看的是布局的ID,而不是布局的實例。

Fragment的生命周期反應Activity的生命周期。

Fragment在顯示和退出時會走一遍完整的生命周期。此外,正在顯示時,就跟Activity的一樣,Activity被onPause,里面的Fragment就onPause,以此類推,由此帶來的問題就是,比如你在onStart()里面做了一些事情,那么,當宿主Activity被擋住,又出現(xiàn)時(比如接了個電話),F(xiàn)ragment的onStart也會被高到,所以你要想到,這些生命周期不單單在顯示和退出時會走到。

Fragment的可見性。

這個問題出現(xiàn)在有Fragment棧的時候,也就是說每個Fragment不知道自己是否真的對用戶可見。比如現(xiàn)在是Fragment A,又在其上面顯示了Fragment B,當B顯示后,A并不知道自己上面還有一個,也不知道自己對用戶不可見了,同樣再有一個C,B也不知。C退出后,B依然不知自己已在棧頂,對用戶可見,B退后,A也不知。也就是說Fragment顯示或者退出,棧里的其他Fragment無法感知。這點就不如Activity,a被b蓋住后,a會走到onStop(),同樣c顯示后,b也能通過onStop()感知。Fragment可以從FragmentManager監(jiān)聽BackStackState的變化,但它只告訴你Stack變了,不告訴你是多了,還是少,還有你處的位置。有一個解決方案就是,記錄頁面的Path深度,再跟Fragment所在的Stack深度來比較,如果一致,那么這個Fragment就在棧頂。因為每個頁面的Path深度是固定的,而Stack深度是不變化的,所以這個能準確的判斷Fragment是否對用戶可見,當然,這個僅針對整個頁面有效,對于布局中的一個區(qū)域是無效的。

Fragment的事件傳遞。

對于層疊的Fragment,其實就相當于在一個FrameLayout里面加上一堆的View,所以,如果處于頂層的Fragment沒處理點擊事件,那么事件就會向下層傳遞,直到事件被處理。比如有二個Fragment A和B,B在A上面,B只有TextView且沒處理事件,那么點擊B時,會發(fā)現(xiàn)A里的View處理了事件。這個對于Activity也不會發(fā)生,因為事件不能跨窗體傳播,上面的Activity沒處理事件,也不會傳給下面的Activity,即使它可見。解決之法,就是讓上面的Fragment的根布局吃掉事件,為每個根ViewGroup添加onClick=“true”。

與第三方Activity交互。與第三方交互,仍要采用Android的標準startActivityForResult()和onActivityResult()這二個方法來進行。但對于Fragment有些事情需要注意,F(xiàn)ragment也有這二個方法,但是為了能正確的讓Fragment收到onActivityResult(),需要:

宿主Activity要實現(xiàn)一個空的onActivityResult(),里面調(diào)用super.onActivityResult()

調(diào)用Fragment#startActivityForResult()而不是用Activity的 當然,也可以直接使用Activity的startActivityForResult(),那樣的話,就只能在宿主Activity里處理返回的結(jié)果了。

小結(jié)

在用法的代碼部分參考郭神的博客,感覺郭神在代碼講解部分通俗易懂,看起來也方便。總之,在使用Fragment也有一些注意事項,不是那么完美的,雖然谷歌推薦我們用Fragment來代替Activity來使用,我們也確實這做了,現(xiàn)在基本主流的APP也都是少量Activity+很多Fragment,但也需要避免有些坑慎入。

參考地址

1,https://developer.android.com/guide/components/fragments.html

2,http://blog.csdn.net/guolin_blog/article/details/8881711

3,http://toughcoder.net/blog/2014/10/22/effective-android-ui-architecture

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,156評論 6 529
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,866評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 174,880評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,398評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,202評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,743評論 1 320
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,822評論 3 438
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 41,962評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,476評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,444評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,579評論 1 365
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,129評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,840評論 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,231評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,487評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,177評論 3 388
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,568評論 2 370

推薦閱讀更多精彩內(nèi)容