1. 程式人生 > >關於切換Fragment的不重新例項化的解決方法

關於切換Fragment的不重新例項化的解決方法

我一般用的replace()方法去切換Fragment,當你只寫靜態頁面的時候是看不出什麼區別的,可當你和伺服器互動時你就會發現,即便是已經顯示過的Fragment還是會被重新例項化,因為replace是會先remove然後add的,所以每次都會執行onDestroyView方法、onCreateView方法。

怎樣做到不用重新例項化呢?查閱資料得知,說是用hide和show來顯示隱藏,所以首先我想的是直接在xml中直接先定義,然後我就這麼做了,然後是通過mFragmentManager.findFragment()來找佈局檔案中定義的fragment,但是卻爆出了Attempt to write to field 'int android.app.Fragment.mNextAnim' on a null obj

這個錯誤,再次查閱資料,有的說是用tag來找,於是我也這麼幹了,結果沒什麼用,一樣的錯誤,跑了斷點,發現是fragment為空!Why?為什麼沒找到!然後我才發現我這個上下文有點不一樣,我是在fragment中又嵌套了fragment,我想,可能是FragmentManager是屬於Activity的上下文的,所以會找不到。於是我試了試,在Activity的xml中定義了一個fragment,然後果然找到了!如果我的環境限制必須要在fragment中巢狀fragment而且還要能不重新例項化,這樣的話該怎麼實現呢?

我能想到的只有在程式碼中動態的add了,思路是,第一次判斷有沒有這個fragment(按照tag找),如果沒有就add一個,如果有就show,都要同時把其他的hide。

Fragment fragment1;
Fragment fragment2;
transaction = mFragmentManager.beginTransaction();
switch (checkedId) {
    case R.id.tab1:
        //先找到要顯示的fragment
fragment1 = mFragmentManager.findFragmentByTag("tab1");
//因為在onCreateView的時候已經把第一個add進去預設顯示了,所以不用判斷是否為空
if (fragment1.isAdded())
            transaction
.show(fragment1); //找到要隱藏的,如果為空,則不需要隱藏 fragment2 = mFragmentManager.findFragmentByTag("tab2"); if (fragment2!=null){ if (fragment2.isAdded()) transaction.hide(fragment2); } break; case R.id.tab2: //先找到要隱藏的fragment fragment1 = mFragmentManager.findFragmentByTag("tab1"); //因為在onCreateView的時候已經把第一個add進去預設顯示了,所以不用判斷是否為空,直接hide if (fragment1.isAdded()) transaction.hide(fragment1); //找到要顯示的fragment,如果能找到就show,找不到就add fragment2 = mFragmentManager.findFragmentByTag("tab2"); if (fragment2!=null){ if (fragment2.isAdded()) transaction.show(fragment2); }else{ Fragment fragment = new Main2_Tab2(); transaction.add(R.id.tab_container, fragment, "tab2"); } break; } transaction.commit();
我這裡寫的只是適合我的此時環境,規範上應該每次找到的fragment都應該判斷是否為空。

初始化的時候add進第一個要顯示的fragment:

private void init() {
    //一開始載入第一個Fragment
Fragment fragment1 = new Main2_Tab1();
transaction = mFragmentManager.beginTransaction();
transaction.add(R.id.tab_container, fragment1, "tab1").commit();
}
值得注意的是,每次add都要指定tag,這樣才能依靠tag找到fragment。

那如果fragment多的時候我每次hide的時候都要hide好多fragment,這樣程式碼未免太冗餘,我是這麼解決的:

首先,初始化的時候一樣:

//預設首先顯示第一個Fragment
mFragment = new Main_1();
mFragmentManager.beginTransaction().add(R.id.container, mFragment, "bottom1").commit();
然後在需要監聽的方法裡,比如onClick裡切換
mTransaction = mFragmentManager.beginTransaction();
//要新增的新的fragment
Fragment frag ;
//已新增的
Fragment mFr;
switch (v.getId()) {
    case R.id.main_bottom_button_first:
        //切換到第一頁
mFr = mFragmentManager.findFragmentByTag("bottom1");
frag = new Main_1();
switchFragment(mFr,frag,"bottom1");
//                Log.d("Main", "切換到: "+"1Fragment");
break;
    case R.id.main_bottom_button_second:
        //切換到第二頁
mFr = mFragmentManager.findFragmentByTag("bottom2");
frag = new Main_2();
switchFragment(mFr, frag, "bottom2");
//                Log.d("Main", "切換到: " + "2Fragment");
break;
    case R.id.main_bottom_button_third:
        //切換到第三頁
mFr = mFragmentManager.findFragmentByTag("bottom3");
frag = new Main_3();
switchFragment(mFr, frag, "bottom3");
//                Log.d("Main", "切換到: " + "3Fragment");
break;
    case R.id.main_bottom_button_four:
        //切換到第四頁
mFr = mFragmentManager.findFragmentByTag("bottom4");
frag = new Main_4();
switchFragment(mFr, frag, "bottom4");
//                Log.d("Main", "切換到: " + "4Fragment");
break;
    case R.id.main_bottom_button_five:
        //切換到第四頁
mFr = mFragmentManager.findFragmentByTag("bottom5");
frag = new Main_5();
switchFragment(mFr, frag, "bottom5");
//                Log.d("Main", "切換到: " + "5Fragment");
break;
}
switchFragment()方法是關鍵,第一個引數是find的那個Fragment(要顯示的),第二個引數是要add的Fragment(要顯示的),第三個引數是tag(為了add時使用):
/*
同一顯示隱藏所有的Fragment
*/
private void switchFragment(Fragment mFr,Fragment frag,String tag) {
        //隱藏其他的Fragment
Fragment b1 = mFragmentManager.findFragmentByTag("bottom1");
        if (b1 != null) {
            if (b1.isAdded())
                mTransaction.hide(b1);
}
        Fragment b2 = mFragmentManager.findFragmentByTag("bottom2");
        if (b2 != null) {
            if (b2.isAdded())
                mTransaction.hide(b2);
}
        Fragment b3 = mFragmentManager.findFragmentByTag("bottom3");
        if (b3 != null) {
            if (b3.isAdded())
                mTransaction.hide(b3);
}
        Fragment b4 = mFragmentManager.findFragmentByTag("bottom4");
        if (b4 != null) {
            if (b4.isAdded())
                mTransaction.hide(b4);
}
        Fragment b5 = mFragmentManager.findFragmentByTag("bottom5");
        if (b5 != null) {
            if (b5.isAdded())
                mTransaction.hide(b5);
}
        if (mFr==null)
            mTransaction.add(R.id.container,frag,tag);
        else{
            mTransaction.show(mFr);
}
        mTransaction.commit();
}
首先隱藏所有已經存在的Fragment,然後判斷要顯示的Fragment找沒找到,找到了就show,沒找到就add,最後統一commit,我試過每次add、hide或者show都commit,發現會報錯java.lang.IllegalStateException:Cannot forward a response that is already committed

還有,

CompoundButton.OnCheckedChangeListener

這個監聽器如果你這同一個RadioGroup裡的RadioButton都添加了這個監聽,則每個按鈕的check改變都會執行這個方法,對於切換fragment來說就會造成混亂,你可以只給這一組裡的某一個RadioButton設定這個監聽試試,畢竟其他的都是聯動影響的(我沒試過)。我發現RadioGroup也有一個監聽介面

RadioGroup.OnCheckedChangeListener

實現這個介面去根據id判斷當前是哪一個RadioButton然後去切換就好了。

如果你是在Activity中切換fragment,那麼直接在xml中定義,然後show、hide應該也沒問題,並且那樣的話不用去頻繁的判斷是否為空、add等操作,會比較簡潔,但是對於在Fragment中又嵌套了Fragment的情況就不能那麼做了。

這是目前我能想到的解決Fragment不用重新例項化的最好方法了,歡迎大神提供更簡潔的方法。

注意注意,如果我需要在fragment初始化的時候想做一些事情,比如我這裡,想要在fragment被hide的時候讓切換ViewPager的timer取消,等到再次顯示時繼續自動切換怎麼辦,要知道此時hide和show都不會執行任何正常的生命週期方法,哪怕是onPause和onResume,此時Fragment只能重寫一個方法

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden){
        if (mTimer != null) {
            mTimer.cancel();
mTimer = null;
}
    }else{
        //開啟新執行緒去後臺自動切換ViewPager
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
            @Override
public void run() {
                //Handler去迴圈切換pager
mHandler.sendEmptyMessage(0);
}
        }, 3000, 3000);
}
}
通過hidden來判斷隱藏或者顯示(hidden為true是隱藏)的時候需要做的事!!