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