關於切換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
我能想到的只有在程式碼中動態的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我這裡寫的只是適合我的此時環境,規範上應該每次找到的fragment都應該判斷是否為空。.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();
初始化的時候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", "切換到: "+"第1個Fragment"); break; case R.id.main_bottom_button_second: //切換到第二頁 mFr = mFragmentManager.findFragmentByTag("bottom2"); frag = new Main_2(); switchFragment(mFr, frag, "bottom2"); // Log.d("Main", "切換到: " + "第2個Fragment"); break; case R.id.main_bottom_button_third: //切換到第三頁 mFr = mFragmentManager.findFragmentByTag("bottom3"); frag = new Main_3(); switchFragment(mFr, frag, "bottom3"); // Log.d("Main", "切換到: " + "第3個Fragment"); break; case R.id.main_bottom_button_four: //切換到第四頁 mFr = mFragmentManager.findFragmentByTag("bottom4"); frag = new Main_4(); switchFragment(mFr, frag, "bottom4"); // Log.d("Main", "切換到: " + "第4個Fragment"); break; case R.id.main_bottom_button_five: //切換到第四頁 mFr = mFragmentManager.findFragmentByTag("bottom5"); frag = new Main_5(); switchFragment(mFr, frag, "bottom5"); // Log.d("Main", "切換到: " + "第5個Fragment"); 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是隱藏)的時候需要做的事!!