Fragment的應(yīng)用真的是越來越廣泛了,之前Android在3.0版本加入Fragment的時(shí)候,主要是為了解決Android Pad屏幕比較大,空間不能充分利用的問題。但隨著界面布局的復(fù)雜化,處理起來也更加的復(fù)雜,引入Fragment可以把a(bǔ)ctivity拆分成各個(gè)部分。每個(gè)Fragment都有它自己的布局和生命周期。
一、Fragment的生命周期
-
onAttach()
作用:fragment已經(jīng)關(guān)聯(lián)到activity。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.i("onAttach_Fragment");
}
該方法有一個(gè)Activity類型的參數(shù),代表綁定的Activity,獲得activity的傳遞的值 就可以進(jìn)行 與activity的通信里, 當(dāng)然也可以使用getActivity(),前提是這個(gè)fragment已經(jīng)和宿主的activity關(guān)聯(lián),并且沒有脫離。
- **onCreate() **
作用:初始化Fragment,系統(tǒng)創(chuàng)建fragment的時(shí)候回調(diào)該方法,在該方法里面實(shí)例化一些變量,參數(shù)是:Bundle savedInstance, 用于保存 Fragment 參數(shù), Fragement 也可以重寫 onSaveInstanceState(BundleoutState) 方法, 保存Fragement狀態(tài)。 -
onCreateView()
作用:初始化Fragment的布局。加載布局和findViewById的操作通常在此函數(shù)內(nèi)完成,當(dāng)系統(tǒng)用到fragment的時(shí)候 fragment就要返回它的view,越快越好 ,所以盡量在這里不要做耗時(shí)操作,比如從數(shù)據(jù)庫加載大量數(shù)據(jù),可進(jìn)行各種判斷省得每次都要加載,減少資源消耗,實(shí)例如下:
if(text==null){
Bundle args=getArguments();
text=args.getString("text");
}
if (view == null) {
view = inflater.inflate(R.layout.hello, null);
}
-
onActivityCreated()
作用:初始化那些你需要你的父Activity或者Fragment的UI已經(jīng)被完整初始化才能初始化的元素。
執(zhí)行該方法時(shí),與Fragment綁定的Activity的onCreate方法已經(jīng)執(zhí)行完成并返回,在該方法內(nèi)可以進(jìn)行與Activity交互的UI操作,當(dāng)執(zhí)行onActivityCreated()的時(shí)候 activity的onCreate才剛完成。所以在onActivityCreated()調(diào)用之前 activity的onCreate可能還沒有完成,所以不能再onCreateView()中進(jìn)行 與activity有交互的UI操作,UI交互操作可以在onActivityCreated()里面進(jìn)行。 -
onStart()
和activity一致,啟動(dòng)Fragement 啟動(dòng)時(shí)回調(diào),,此時(shí)Fragement由不可見變?yōu)榭梢姞顟B(tài)。 -
onResume()
執(zhí)行該方法時(shí),F(xiàn)ragment處于活動(dòng)狀態(tài),用戶可與之交互。激活Fragement 進(jìn)入前臺(tái), 可獲取焦點(diǎn)時(shí)激活。 -
onPause()
和activity一致 其他的activity獲得焦點(diǎn),這個(gè)Fragment仍然可見,但是用戶不能與之交互。第一次調(diào)用的時(shí)候,指的是 用戶 離開這個(gè)Fragment(并不是被銷毀)。 -
onStop()
和activity一致, fragment不可見的, 可能情況:activity被stopped了或者 fragment被移除但被加入到回退棧中,一個(gè)stopped的fragment仍然是活著的如果長(zhǎng)時(shí)間不用也會(huì)被移除。 -
onDestroyView()
Fragment中的布局被移除時(shí)調(diào)用。
表示Fragment銷毀相關(guān)聯(lián)的UI布局, 清除所有跟視圖相關(guān)的資源,但未與Activity解除綁定,依然可以通過onCreateView方法重新創(chuàng)建視圖。 -
onDestroy()
銷毀Fragment。通常按Back鍵退出或者Fragment被回收時(shí)調(diào)用此方法。 -
onDetach()
Fragment解除與Activity的綁定。在onDestroy方法之后調(diào)用。
下面給出activity和fragment同時(shí)運(yùn)行時(shí)候的生命周期:
- 開始啟動(dòng):
03-10 16:55:57.722 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onCreate() 方法執(zhí)行!
03-10 16:55:57.728 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onCreate() 方法執(zhí)行!
03-10 16:55:57.728 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onCreateView() 方法執(zhí)行!
03-10 16:55:57.728 1700-1700/com.liujc.fragmentlife D/TestFragment: 沒有保存的數(shù)據(jù)!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onActivityCreated() 方法執(zhí)行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onStart() 方法執(zhí)行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onStart() 方法執(zhí)行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onResume() 方法執(zhí)行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onResume() 方法執(zhí)行!
細(xì)心的你可能會(huì)發(fā)現(xiàn)為什么Fragment沒走onAttach()方法呢?難道生命周期還有問題不成。其實(shí)Fragment的onAttach()方法有2個(gè)重載onAttach(Context context)和onAttach(Activity activity),我的測(cè)試機(jī)用的android 5.0系統(tǒng),而在API低于 23 的版本中不會(huì)去調(diào)用onAttach(Context context),只會(huì)去調(diào)用onAttach(Activity)。然后把兩個(gè)方法都加上運(yùn)行一下結(jié)果如下:
03-10 17:19:08.539 19010-19010/com.liujc.fragmentlife D/MainActivity: Activity onCreate() 方法執(zhí)行!
03-10 17:19:08.546 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onAttach(Activity activity) 方法執(zhí)行!
03-10 17:19:08.546 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onCreate() 方法執(zhí)行!
03-10 17:19:08.547 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onCreateView() 方法執(zhí)行!
03-10 17:19:08.547 19010-19010/com.liujc.fragmentlife D/TestFragment: 沒有保存的數(shù)據(jù)!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onActivityCreated() 方法執(zhí)行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/MainActivity: Activity onStart() 方法執(zhí)行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onStart() 方法執(zhí)行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/MainActivity: Activity onResume() 方法執(zhí)行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onResume() 方法執(zhí)行!
- 按下home按鍵
03-10 17:00:08.455 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onPause() 方法執(zhí)行!
03-10 17:00:08.456 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onPause() 方法執(zhí)行!
03-10 17:00:09.048 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onSaveInstanceState() 方法執(zhí)行!
03-10 17:00:09.052 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onStop() 方法執(zhí)行!
03-10 17:00:09.054 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onStop() 方法執(zhí)行!
- 再回到界面
03-10 17:01:20.870 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onRestart() 方法執(zhí)行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onStart() 方法執(zhí)行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onStart() 方法執(zhí)行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onResume() 方法執(zhí)行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onResume() 方法執(zhí)行!
- 銷毀activity
03-10 17:05:53.900 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onPause() 方法執(zhí)行!
03-10 17:05:53.901 22559-22559/com.liujc.fragmentlife D/MainActivity: Activity onPause() 方法執(zhí)行!
03-10 17:05:54.435 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onStop() 方法執(zhí)行!
03-10 17:05:54.435 22559-22559/com.liujc.fragmentlife D/MainActivity: Activity onStop() 方法執(zhí)行!
03-10 17:05:54.437 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onDestroyView() 方法執(zhí)行!
03-10 17:05:54.441 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onDestroy() 方法執(zhí)行!
03-10 17:05:54.441 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onDetach() 方法執(zhí)行!
03-10 17:05:54.441 22559-22559/com.liujc.fragmentlife D/MainActivity: Activity onDestroy() 方法執(zhí)行!
可以看出 當(dāng)現(xiàn)實(shí)fragment的時(shí)候都先執(zhí)行activity方法,當(dāng)銷毀的時(shí)候都是現(xiàn)執(zhí)行 fragment的方法,這樣更好理解fragment是嵌套在activity中。
二、將Fragment添加到Activity之中
可以通過在Activity布局文件中聲明Fragment,用Fragment標(biāo)簽把Fragment插入到Activity的布局中,或者是用應(yīng)用程序源碼將它添加到一個(gè)存在的ViewGroup中。但Fragment并不是一個(gè)定要作為Activity布局的一部分,F(xiàn)ragment也可以為Activity隱身工作。
-
在activity的布局文件里聲明fragment。
可以像為view一樣為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.liujc.test.FragmentOne"
android:id="@+id/fragment_one"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
fragment標(biāo)簽中的android:name 屬性指定了布局中實(shí)例化的Fragment類。
當(dāng)系統(tǒng)創(chuàng)建activity布局時(shí),它實(shí)例化了布局文件中指定的每一個(gè)fragment,并為它們調(diào)用onCreateView()函數(shù),以獲取每一個(gè)fragment的布局。系統(tǒng)直接在元素的位置插入fragment返回的View。
注意:每個(gè)fragment都需要一個(gè)唯一的標(biāo)識(shí),如果重啟activity,系統(tǒng)可用來恢復(fù)fragment(并且可用來捕捉fragment的事務(wù)處理,例如移除)。
為fragment提供ID有三種方法:
- 用android:id屬性提供一個(gè)唯一的標(biāo)識(shí)。
- 用android:tag屬性提供一個(gè)唯一的字符串。
- 如果上述兩個(gè)屬性都沒有,系統(tǒng)會(huì)使用其容器視圖(view)的ID。
-
通過編碼將fragment添加到已存在的ViewGroup中。
在activity運(yùn)行的任何時(shí)候,你都可以將fragment添加到activity布局中。要管理activity中的fragment,可以使用FragmentManager??梢酝ㄟ^在activity中調(diào)用getFragmentManager()獲得。使用FragmentManager 可以做如下事情,包括:
- 使用findFragmentById()(用于在activity布局中提供有界面的fragment)或者findFragmentByTag()獲取activity中存在的fragment(用于有界面或者沒有界面的fragment)。
- 使用popBackStack()(模仿用戶的BACK命令)從后臺(tái)棧彈出fragment。
- 使用addOnBackStackChangedListener()注冊(cè)一個(gè)監(jiān)聽后臺(tái)棧變化的監(jiān)聽器。
在Android中,對(duì)Fragment的事務(wù)操作都是通過FragmentTransaction來執(zhí)行。
FragmentTransaction transaction = fm.benginTransatcion();//開啟一個(gè)事務(wù)
操作大致可以分為兩類:
-
顯示:
add()
、replace()
、show()
、attach()
。
**transaction.add() **
往Activity中添加一個(gè)Fragment。
**transaction.replace() **
使用另一個(gè)Fragment替換當(dāng)前的,實(shí)際上就是remove()然后add()的合體。
**transaction.show() **
顯示之前隱藏的Fragment。
**attach() **
重建view視圖,附加到UI上并顯示。 -
隱藏:
remove()
、hide()
、detach()
。
** transaction.remove() **
從Activity中移除一個(gè)Fragment,如果被移除的Fragment沒有添加到回退棧(回退棧后面會(huì)詳細(xì)說),這個(gè)Fragment實(shí)例將會(huì)被銷毀。
transaction.hide()
隱藏當(dāng)前的Fragment,僅僅是設(shè)為不可見,并不會(huì)銷毀。
detach()
會(huì)將view從UI中移除,和remove()不同,此時(shí)fragment的狀態(tài)依然由FragmentManager維護(hù)。
注意:
- 調(diào)用show() & hide()方法時(shí),F(xiàn)ragment的生命周期方法并不會(huì)被執(zhí)行,僅僅是Fragment的View被顯示或者?隱藏。
- 執(zhí)行replace()時(shí)(至少兩個(gè)Fragment),會(huì)執(zhí)行第二個(gè)Fragment的onAttach()方法、執(zhí)行第一個(gè)Fragment的onPause()-onDetach()方法,同時(shí)containerView會(huì)detach第一個(gè)Fragment的View。
- 執(zhí)行add()方法執(zhí)行onAttach()-onResume()的生命周期,相對(duì)的remove()就是執(zhí)行完成剩下的onPause()-onDetach()周期。
add方式實(shí)現(xiàn)fragment的效果就是:切換fragment時(shí)不會(huì)重新創(chuàng)建,是什么樣子切換回來還是什么樣子;
用replace的效果就是:切換fragment時(shí)每次都會(huì)重新創(chuàng)建初始化。
從Activity中取得FragmentTransaction的實(shí)例:
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
用add()函數(shù)添加fragment,并指定要添加的fragment以及要將其插入到哪個(gè)視圖(view)之中(注意commit事務(wù)):
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
- 添加沒有界面的fragment。
也可以使用fragment為activity提供后臺(tái)動(dòng)作,卻不呈現(xiàn)多余的用戶界面。
想要添加沒有界面的fragment ,可以使用add(Fragment, String)(為fragment提供一個(gè)唯一的字符串“tag”,而不是視圖(view)ID)。這樣添加了fragment,但是,因?yàn)檫€沒有關(guān)聯(lián)到activity布局中的視圖(view) ,收不到onCreateView()的調(diào)用。所以不需要實(shí)現(xiàn)這個(gè)方法。對(duì)于無界面fragment,字符串標(biāo)簽是唯一識(shí)別它的方法。如果之后想從activity中取到fragment,需要使用findFragmentByTag()。
三、Fragment與Activity交互
- Activity中已經(jīng)有了該Fragment的引用,直接通過該引用進(jìn)行交互。
如果沒引用可以通過調(diào)用fragment的函數(shù)findFragmentById()或者findFragmentByTag(),從FragmentManager中獲取Fragment的索引,例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
- 在Fragment中可以通過getActivity得到當(dāng)前綁定的Activity的實(shí)例。
- 創(chuàng)建activity事件回調(diào)函數(shù),在fragment內(nèi)部定義一個(gè)回調(diào)接口,宿主activity來實(shí)現(xiàn)它。
四、Fragment事務(wù)后臺(tái)棧
在調(diào)用commit()之前,可以將事務(wù)添加到fragment事務(wù)后臺(tái)棧中(通過調(diào)用addToBackStatck())。這個(gè)后臺(tái)棧由activity管理,并且允許用戶通過按BACK鍵回退到前一個(gè)fragment狀態(tài)。
下面的代碼中一個(gè)fragment代替另一個(gè)fragment,并且將之前的fragment狀態(tài)保留在后臺(tái)棧中:
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
注意:
- 如果添加多個(gè)變更事務(wù)(例如另一個(gè)add()或者remove())并調(diào)用addToBackStack(),那么在調(diào)用commit()之前的所有應(yīng)用的變更被作為一個(gè)單獨(dú)的事務(wù)添加到后臺(tái)棧中,并且BACK鍵可以將它們一起回退。
- 當(dāng)移除一個(gè)fragment時(shí),如果調(diào)用了addToBackStack(),那么之后fragment會(huì)被停止,如果用戶回退,它將被恢復(fù)過來。
- 調(diào)用commit()并不立刻執(zhí)行事務(wù),相反,而是采取預(yù)約方式,一旦activity的界面線程(主線程)準(zhǔn)備好便可運(yùn)行起來。然而,如果有必要的話,你可以從界面線程調(diào)用executePendingTransations()立即執(zhí)行由commit()提交的事務(wù)。
- 只能在activity保存狀態(tài)(當(dāng)用戶離開activity時(shí))之前用commit()提交事務(wù)。如果你嘗試在那時(shí)之后提交,會(huì)拋出一個(gè)異常。這是因?yàn)槿绻鸻ctivity需要被恢復(fù),提交后的狀態(tài)會(huì)被丟失。對(duì)于這類丟失提交的情況,可使用commitAllowingStateLoss()。
五、Fragment的setUserVisibleHint()
Android應(yīng)用開發(fā)過程中,ViewPager同時(shí)加載多個(gè)fragment,以實(shí)現(xiàn)多tab頁面快速切換, 但是fragment初始化時(shí)若加載的內(nèi)容較多,就可能導(dǎo)致整個(gè)應(yīng)用啟動(dòng)速度緩慢,影響用戶體驗(yàn)。 為了提高用戶體驗(yàn),我們會(huì)使用一些懶加載方案,實(shí)現(xiàn)加載延遲。這時(shí)我們會(huì)用到getUserVisibleHint()與setUserVisibleHint()這兩個(gè)方法。
/**
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = !isVisibleToUser;
}
/**
* @return The current value of the user-visible hint on this fragment.
* @see #setUserVisibleHint(boolean)
*/
public boolean getUserVisibleHint() {
return mUserVisibleHint;
}
從上述源碼注釋我們可以看出,當(dāng)fragment被用戶可見時(shí),setUserVisibleHint()會(huì)調(diào)用且傳入true值,當(dāng)fragment不被用戶可見時(shí),setUserVisibleHint()則得到false值。而在傳統(tǒng)的fragment生命周期里也看不到這個(gè)函數(shù)。可以看出其實(shí)這個(gè)setUserVisibleHint()方法算是手動(dòng)調(diào)用的,并不是在Fragment的生命周期中自動(dòng)調(diào)用。