引言:這篇文章,大概分析下Fragment的生命周期、實際應用方法以及使用Fragment時需要注意的地方,算是Fragment的入門級文章,理解透Fragment生命周期和一些細節,就容易理解Fragment如何與外界通信等問題了。至于對其的源碼分析等更加深入的內容,本文涉及不多。
Fragment的寫法就不多說了,一般是繼承Fragment,然后重寫onCreateView
方法去與View布局進行綁定。
<u>PS:本人拋磚引玉,某些方面沒有說明到,還請讀者朋友們多多包容</u>。
使用前提
FragmentActivity。使用Fragment,需要Activity繼承自FragmentActivity,并且,為了兼容到Android3.0以前的版本,需要使用v4兼容包下的FragmentActivity。
示例說明用法
一、常見的兩種加載方式
- 方式一:直接獲取FragmentManager并使用FragmentManager管理下的FragmentTranscation來進行Fragment或者Fragment列表的加載、替換、刪除等操作,此時的Fragment所在的容器一般選擇用FrameLayout。
【關于FragmentManager和FragmentTranscation】
* FragmentManager:Activity中有個FragmentManager,其內部維護fragment隊列,以及fragment事務的回退棧。在Fragment被創建、并由FragmentManager管理時,FragmentManager就把它放入自己維護的fragment隊列中。
* FragmentTransaction:知道了FragmentManger可以管理和維護Fragment,那么FragmentManager是直接去綁定Fragment然后把它set進自己的隊列中嗎?不是的,而是用FragmentTransaction(Fragment事務),FragmentManager調用beginTransaction()方法返回一個新建的事務,用于記錄對于Fragment的add、replace等操作,最終將事務commit回FragmentManager,才開始啟動執行事務的內容,實現真正的Fragment顯示。
- 方式二:使用ViewPager等容器去裝載Fragment列表并通過他們自己的頁面切換能力去切換Fragment。
【關于ViewPager適配器相關類:FragmentPagerAdapter與FragmentStatePagerAdapter】
* FragmentPagerAdapter:對于不再需要的fragment,選擇調用detach方法,僅銷毀視圖,并不會銷毀fragment實例。
* FragmentStatePagerAdapter:會銷毀不再需要的fragment,當當前事務提交以后,會徹底的將fragment從當前Activity的FragmentManager中移除,state標明,銷毀時,會將其onSaveInstanceState(Bundle outState)中的bundle信息保存下來,當用戶切換回來,可以通過該bundle恢復生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中保存一些數據,在onCreate中進行恢復創建。
* 總結:<u>使用FragmentStatePagerAdapter當然更省內存,但是銷毀新建也是需要時間的。一般情況下,如果你是制作主頁面,就3、4個Tab,那么可以選擇使用FragmentPagerAdapter,如果你是用于ViewPager展示數量特別多的條目時,那么建議使用FragmentStatePagerAdapter。</u>
(這種方式中ViewPager管理Fragment的原理其實也是FragmentManager和FragmentTransaction,看下圖源碼就知道)
二、兩種寫法的關鍵代碼
- 直接獲取FragmentManager和它的FragmentTransaction去將Fragment加載到當前界面的指定容器中去的一般寫法:
- activity layout布局
```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
…………
<Button
……………………
/>
…………
<FrameLayout
android:id="@+id/frame_root_fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>
2. Activity的初始化方法
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
fragmentA = new MyFragmentA();
getSupportFragmentManager().beginTransaction().add(R.id.frame_root_fragment,fragmentA).commit();
}
```
- 使用ViewPager去裝載適配Fragment的一般寫法:
- activity layout布局
```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
……
/>
<android.support.v4.view.ViewPager
android:id="@+id/vp_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>
```
2. 初始化方法
///在onCreate()中調用此方法
private void initView() {
///使用LinkedHashMap等方式方便我們管理Fragments
fragmentMap = new LinkedHashMap<>();
///控件初始化
mTabLayout = (TabLayout) findViewById(R.id.tl_main);
mViewPager = (ViewPager) findViewById(R.id.vp_main);
…………
mPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:///Fragment一
default:
if(!fragmentMap.containsKey(titles[0]))
fragmentMap.put(titles[0],new DogFragment());
return fragmentMap.get(titles[0]);
case 1:///Fragment二
if(!fragmentMap.containsKey(titles[1]))
fragmentMap.put(titles[1],new CatFragment());
return fragmentMap.get(titles[1]);
}
}
…………
};
mViewPager.setAdapter(mPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
}
從圖解中分析Fragment的生命周期
下面圖解說明下Fragment的生命周期:
圖解說明:
- 從圖解可以看到,Fragment的一些生命周期方法與Activity比較相似,畢竟Fragment表示“碎片、塊”的含義,本身實現出來一個主要目的就是幫Activity分擔UI代碼部分的實現邏輯的。
- 藍色背景的生命周期方法:從onCreateView() 到onDestroyView(),在整個Fragment創建到被銷毀的過程中可以不只被執行一次,這里就涉及到管理著Fragment事務(FragmentTransaction)的FragmentManager底層的后臺記錄棧的東西了。
- 綠色背景的生命周期方法:在整個Fragment的生命周期中,僅僅執行一次。
-
對圖解的補充:這里用幾個例子具體看清Fragment的周期方法的調用次序,更好地理解Fragment的生命周期:
- 首先,可以看看本人另一篇博文查漏補缺(二):易忘難懂中的
第10點:Fragment 與 它所在的Activity的生命周期方法執行次序
,它說明了在ViewPager適配Fragment列表這種方式下Fragment方法執行次序。 - 這里再加上<u>直接在frameLayout上添加、替換Fragment</u>的情況,進一步分析Fragment生命周期:
- 執行如下代碼,來加載Fragment時
- 首先,可以看看本人另一篇博文查漏補缺(二):易忘難懂中的
fragmentA = new MyFragmentA()
getSupportFragmentManager().beginTransaction()
.add(R.id.frame_root_fragment,fragmentA);///執行代碼①
//getSupportFragmentManager().beginTransaction()
// .add(R.id.frame_root_fragment,fragmentA).commit();//執行代碼②
得到執行Log截圖:
【執行代碼①】

【執行代碼②】

2. 退出Activity操作時
得到Log截圖:

3. Fragment中點擊按鈕執行startActivityForResult()方法時
gif圖:

執行**`Fragment.startActivityForResult()`**Log截圖:

執行**`getActivity().startActivityForResult()`**Log截圖:

* 由上可得到</u>***總結***</u>:
* new 一個Fragment,Fragment只是調用了自身的空參數構造方法,并沒有其他操作。
* Fragment要執行其onAttach()及其之后的生命周期方法,需要被FragmentTransition記錄并真正提交到FragmentManager處,才能實現。
* 如果Fragment要準備顯示了(即被事務提交到FragmentManager,具體后面會說到),那么它再繼續執行onCreateView()到onResume()的方法,去創建和綁定UI,最后在前臺顯示。
* 由于ViewPager在Activity.onCreate()中并沒有真正初始化界面完成,即在onCreate()階段中,ViewPager適配器內部并沒有真的調用Fragment的構造方法并從FragmentManager中獲取FragmentTransaction事務來記錄Fragment的操作,等到Activity.onResume()方法被執行(即Activity啟動完成并在前臺顯示了),這時,ViewPager才選取默認要顯示的那一頁,并對應執行這一頁的Fragment的創建+事務添加+提交給Manager的一個過程。
* 在Activity被停止或即將被銷毀的過程中,都首先停止或銷毀它內部的所有Fragment,而它內部的所有Fragment則各自檢查自己是否有置于前臺或顯示,如果有,那就得destroyView()銷毀掉它所綁定的View布局并使自己處于完全不可見狀態,再判斷地執行onDestroy()。
* 對于最后一個例子,Fragment執行自身的startActivityForResult()和getActivity().startActivityForResult()是有區別的:如果執行自身的startActivityForResult(),那么在另一個Activity返回時,Fragment就會執行自身的onActivityResult()方法,否則,只會觸發Activity的onActivityResult(),除非你在Activity的onActivityResult()方法中添加幾行代碼,讓它把結果也返回給指定Fragment,如:
```
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (fragmentA!=null)
fragmentA.onActivityResult(requestCode,resultCode,data);
if (fragmentB!=null)
fragmentB.onActivityResult(requestCode,resultCode,data);
}
```
Fragment開發遇到的問題和解決(Fragment注意點)
一、Fragment 與 后臺事務棧管理
前面說了Fragment的幾個生命周期方法可能不止執行一次,關鍵點就在于Fragment是否被事務管理到后臺棧中,這里就涉及到與Fragment有關的幾個類的相關方法:
- FragmentTransaction.addToBackStack(String)【關鍵:讓攜帶著Fragment記錄的事務保存到后臺棧中】
- FragmentActivity.onBackPressed()【點擊BACK按鍵或者其他方式觸發Activity的回退方法時,會促發此方法,FragmentActivity的此方法中會多加一個判斷,看后臺棧中是否存在事務,存在,則一個事務出棧,這個事務對應的Fragment操作記錄則被回退清空,也就是這個事務下Fragment的操作,全部撤銷,Fragment會從FragmentManager維護的Fragment隊列中拿出并被銷毀從而執行它的生命周期的余下方法】
- FragmentManager.popBackStack()【Manager管理的后臺棧的一個出棧操作,返回一個FragmentTransaction事務,但是此操作要等到Application返回它是事件loop時才會觸發】
- FragmentManager.popBackStackImmediate()【與popBackStack()方法差不多,只是它是立即執行出棧操作,而不用顧忌Application】
下面舉個例子,給大家看看Fragment是怎么跑的:
【這里使用FragmentTransaction.replace()作為主要Fragment操作】
原因:
?add()方法與replace()方法大多數情況下效果是一樣的,add是指“添加”,replace是指“替換”,一般使用同一個FrameLayout去加載Fragment時,推薦是用replace()的,省去add的多層Fragment重疊,當然,在需要進行輪播等需要及時看到多個Fragment的時候,add()比較好的。
?remove()、hide()、show()、detach()、attach()等方法,都相應地只是執行對應Fragment的對應生命周期方法而已
?所以這里用replace()做演示更好。
主要執行的代碼:
/***
* 方式一:單個Fragment 做下記錄到backStack
*
* @param baseFragment
*/
public void onAddFragment(Fragment baseFragment) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frame_root_fragment, baseFragment, "tag");
ft.addToBackStack("tag");
ft.commit();
}
/**
* 方式二:多個Fragment同時addToBackStack
*
* @param fragmentList
*/
public void onAddFragments(Fragment[] fragmentList) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
int pos = 0;
for (Fragment item : fragmentList) {
pos += 1;
ft.replace(R.id.frame_root_fragment, item, "tag" + pos);
ft.addToBackStack("tag" + pos);
}
ft.commit();
}
gif圖:
- 執行方式一代碼(事務一對一Fragment)
-
Log截圖:
- 執行方式二代碼(事務一對多Fragments)
-
Log截圖:
得出結論:
- 從代碼可以看到,FragmentTransaction可以記錄一到多個Fragment的相關操作。
- 示例可以知道,FragmentActivity回退事件發生時,會先把所有的FragmentTransaction事務一一彈出后臺棧先。【如果一個事務對應一個Fragment,那么這里就實現了一個Fragment之間的跳轉過程;而如果一個事務對應多個Fragment,那么,一個事務彈出,它涉及到的后臺隊列中Fragment集合便會一下子都彈出銷毀,而不是一個個Fragment地出隊】
二、Fragment以及它的宿主Activity的復用
在平時開發中,怎么樣方便開發、方便維護、盡量解耦,就怎么寫代碼,這里給一個比較好的Fragment寫法例子(參考鴻洋博客中的一個Fragment例子),供參考:
public class ContentFragment extends Fragment
{
private String mArgument;///Activity傳遞的數據(值)
public static final String ARGUMENT = "argument";///Activity傳遞的數據名(鍵)
public static final String RESPONSE = "response";///Activity
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null)
{
mArgument = bundle.getString(ARGUMENT);
Intent intent = new Intent();
intent.putExtra(RESPONSE, "good");
getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);
}
}
////在實例化時獲取Activity傳入的值(這里示例為String類型)
public static ContentFragment newInstance(String argument)
{
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, argument);
ContentFragment contentFragment = new ContentFragment();
contentFragment.setArguments(bundle);
return contentFragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Random random = new Random();
TextView tv = new TextView(getActivity());
///.........
return tv;
}
}
其實寫法不一,可能有很多更加靈活的寫法,但本文不深究,只要大家有“Fragment的復用”這個思想,旨在寫出容易復用、與Activity耦合度小、存在與外界通信接口的Fragment,就能夠實際減少工作量、理清思路。
下面是一個抽象Activity,用于簡單狀態Fragment 的Activity自身代碼的復用,可參考:
public abstract class SingleFragmentActivity extends FragmentActivity {
protected abstract Fragment createFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment =fm.findFragmentById(R.id.id_fragment_container);
if(fragment == null )
{
fragment = createFragment() ;
fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit();
}
}
}
三、關于DialogFragment
Android3.0被引入的一類特殊Fragment,方便我們構建具有和Fragment一樣生命周期的一類Dialog等組件,從而解決普通的Dialog等組件難以管理它的生命周期、與Activity、Fragment交互的限制。詳細可參考Android 官方推薦 : DialogFragment 創建對話框
四、Fragment與外界的通信
理解了Fragment生命周期和它的基本寫法,那么,再說說Fragment與外界的通信。上面提到的DialogFragment就是用于優化通信和管理的,那么,平常我們使用Fragment,應該如何保證與其他Fragment和外部Activity進行通信呢?
- Fragment與它的宿主Activity通信:
* (Activity傳給Fragment)Activity通過`Fragment.setArguments(Bundle)`在創建Fragment時傳遞數據給Fragment,在Fragment的onCreate()中,Fragment通過`getArguments()`獲取Bundle數據。
* (Activity傳給Fragment)Activity通過`getSupportFragmentManager().findFragmentById()`或`getSupportFragmentManager().findFragmentByTag()`獲取Fragment并調用Fragment自己定義的方法,數據通過參數形式傳給Fragment。
* (Fragment傳給Activity)Fragment通過`getActivity()`方式獲取宿主Activity,就可以調用Activity的方法【但是這樣不嚴謹,Fragment能夠調用的方法,應該要受到限定,所以,使用如下代碼的方式,通過在Fragment中定義一個接口,然后讓宿主Activity實現這些方法】
```
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 implement OnArticleSelectedListener");
}
}
...
* (Activity傳給Fragment、Fragment傳Fragment)還可以通過廣播的方式,讓Fragment去注冊廣播,然后,Fragment調用`getActivity().sendBroadcast(Intent)`或者Activity調用`sendBroadcast(Intent)`來發出廣播,讓注冊了該廣播的Fragment去接收并過濾廣播信息【然而這樣做有些小題大做,而且需考慮廣播的注冊等】 *<u>關鍵代碼</u>*:
public class FragmentA extends Fragment{
MyBroadcast broadcast;
//.........
@Override
public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//........
IntentFilter filter = new IntentFilter();
try {
if (mReceiver != null) {
getActivity().unregisterReceiver(broadcast);
}
} catch (Exception e) {
e.printStackTrace();
}
broadcast = new MyBroadcast();
filter.addAction(PageOneFragment.DATA_CHANGED);
getActivity().registerReceiver(broadcast, filter);
//........
}
//..................
class MyBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(PageOneFragment.DATA_CHANGED)) {
///。。。。。
}
}
}
}
```
- Fragment之間的通信:
* 在同一個Activity下的兩個Fragment的通信:FragmentA調用其宿主Activity的方法,宿主Activity再根據FragmentA的調用參數去調用FragmentB的方法并傳遞參數給B。
* 不同Activity下的兩個Fragment的通信:
* 首先能夠保證兩者都能夠執行到onActivityResult()方法【文章前面有說到】
* (A傳給B)FragmentA通過startActivityForResult()【調用Fragment本身或者getActivity()都可以】的方式,把Intent數據等傳到另一個Activity,然后讓另一個Activity傳值給FragmentB
* (B返回給A)FragmentB處理完,通過getActivity().setResult()把返回的數據設置好,然后返回Activity,A再從它的onActivityResult中拿。
* 同一個Activity下,DialogFragment 與 Fragment的交互:
> 原理:FragmentA中new一個DialogFragment對象,并讓其`setTargetFragment()`來綁定目的Fragment,等DialogFragment處理完數據,調用剛剛綁定的FragmentA的`onActivityResult()`把數據傳回給FragmetnA。
上關鍵代碼:
*<u>Fragment 的代碼</u>*
```
public class ContentFragment extends Fragment
{
//...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//....
tv.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
EvaluateDialog dialog = new EvaluateDialog();
//注意setTargetFragment
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
dialog.show(getFragmentManager(), EVALUATE_DIALOG);
}
});
return tv;
}
//接收返回回來的數據
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_EVALUATE)
{
String evaluate = data
.getStringExtra(EvaluateDialog.RESPONSE_EVALUATE);
Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra(RESPONSE, evaluate);
getActivity().setResult(Activity.REQUEST_OK, intent);
}
}
}
```
*<u>DialogFragment 的代碼</u>*
```
public class EvaluateDialog extends DialogFragment
{
private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" };
public static final String RESPONSE_EVALUATE = "response_evaluate";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Evaluate :").setItems(mEvaluteVals,
new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
setResult(which);
}
});
return builder.create();
}
// 設置返回數據
protected void setResult(int which)
{
// 判斷是否設置了targetFragment
if (getTargetFragment() == null)
return;
Intent intent = new Intent();
intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]);
getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE,
Activity.RESULT_OK, intent);
}
}
```
補充:關于DialogFragment調整窗口大小,可以參考這篇文章
感謝閱讀
對于Fragment的小小講解,就到此結束了,之后會慢慢深入,謝謝大家支持,喜歡的讀者可以關注我一波~~
推薦閱讀:
?本人的幾篇文章:
Android開發細節--查漏補缺(二):易忘難懂
Java面試相關(一)-- Java類加載全過程
?Fragment相關文章:
http://blog.csdn.net/lmj623565791/article/details/37815413
http://blog.csdn.net/lmj623565791/article/details/42628537/
http://www.cnblogs.com/android-joker/p/4414891.html