我一般用的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", "切换到: "+"第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是隐藏)的时候需要做的事!!