以前写多个fragment切换是经常使用这种方法切换fragment:
/**
* 使用replace切换页面
* 显示fragment
*/
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fg);
transaction.commit();
}
replace():该方法只是在上一个Fragment不再需要时采用的简便方法,弊端就是如果需要重复使用该fragment时,需要每次都要重新加载一次。比如我在第一个fragment输入信息后,切换第二个fragment后再切换回去,就会造成数据丢失
而且,如果每切换一次就实例化一次的话,FragmentManager管理下的栈也会爆满,最终会导致手机卡顿,这很明显不是正确的Fragment使用姿势,这时,我们就需要使用show()、hide()、add()了,正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个就行了,代码修改如下:
/**
* 使用show() hide()切换页面
* 显示fragment
*/
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前没有添加过
if(!fg.isAdded()){
transaction
.hide(currentFragment)
.add(R.id.content,fg);
}else{
transaction
.hide(currentFragment)
.show(fg);
}
//全局变量,记录当前显示的fragment
currentFragment = fg;
transaction.commit();
}
即使切换到别的fragment,再切换回来,数据还依然存在,这就避免了Fragment切换时布局重新实例化。
安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复
当我们不退出软件,只是后台挂着去干别的事,当系统内存不足回收我们这个app时,再切换回来,app的这几个Fragment界面会重叠。
三个fragment全部叠在了一起,而且点击上面菜单也不能消除重叠。显然,这并不是我们想要的,没事,继续解决问题,使用findFragmentByTag:即在add()或者replace()时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag找到对应的Fragment,并hide()需要隐藏的fragment。,修改如下:
/**
* 使用show() hide()切换页面
* 显示fragment
*/
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前没有添加过
if(!fg.isAdded()){
transaction
.hide(currentFragment)
.add(R.id.content,fg,fg.getClass().getName()); //第三个参数为当前的fragment绑定一个tag,tag为当前绑定fragment的类名
}else{
transaction
.hide(currentFragment)
.show(fg);
}
currentFragment = fg;
transaction.commit();
}
别急,还没完,在当前Activity的
onCreate()
方法里面添加一下代码:
if (savedInstanceState != null) { // “内存重启”时调用
//从fragmentManager里面找到fragment
fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName());
fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName());
fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName());
//解决重叠问题show里面可以指定恢复的页面
fragmentManager.beginTransaction()
.show(fgOne)
.hide(fgTwo)
.hide(fgThree)
.commit();
//把当前显示的fragment记录下来
currentFragment = fgOne;
}else{ //正常启动时调用
fgOne = new OneFragment();
fgTwo = new TwoFragment();
fgThree = new ThreeFragment();
showFragment(fgOne);
}
OK,当app后台时遇到“内存重启”的情况下,再返回我们的app,就会恢复到
show(fgOne)
页面,而且还不会造成重叠问题!
很显然,这样结束是不道德的,因为有人会问了,如果想记录当前退出的状态以至于下次恢复时直接显示之前的fragment页面怎么办,恩,对于这个问题,我们可以在activity的onSaveInstanceState()
方法中记录一下“内存重启”之前的Fragment的页面,然后在oncreate()
中取出来,根据保存的页面来显示到指定的fragment,代码如下:
@Override
protected void onSaveInstanceState(Bundle outState) {
//“内存重启”时保存当前的fragment名字
outState.putString(STATE_FRAGMENT_SHOW,currentFragment.getClass().getName());
super.onSaveInstanceState(outState);
}
然后在
oncreate()
方法中添加(修改上面的那个代码)
if (savedInstanceState != null) { // “内存重启”时调用
//获取“内存重启”时保存的fragment名字
String saveName = savedInstanceState.getString(STATE_FRAGMENT_SHOW);
//从fragmentManager里面找到fragment
fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName());
fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName());
fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName());
//如果为空就默认操作
if(TextUtils.isEmpty(saveName)){
//解决重叠问题
fragmentManager.beginTransaction()
.show(fgOne)
.hide(fgTwo)
.hide(fgThree)
.commit();
//把当前显示的fragment记录下来
currentFragment = fgOne;
}else{
if(saveName.equals(fgOne.getClass().getName())){ //如果推出之前是OneFragment
//解决重叠问题
fragmentManager.beginTransaction()
.show(fgOne)
.hide(fgTwo)
.hide(fgThree)
.commit();
//把当前显示的fragment记录下来
currentFragment = fgOne;
}else if(saveName.equals(fgTwo.getClass().getName())){ //如果推出之前是TwoFragment
//解决重叠问题
fragmentManager.beginTransaction()
.show(fgTwo)
.hide(fgOne)
.hide(fgThree)
.commit();
//把当前显示的fragment记录下来
currentFragment = fgTwo;
}else{ //如果推出之前是ThreeFragment
//解决重叠问题
fragmentManager.beginTransaction()
.show(fgThree)
.hide(fgTwo)
.hide(fgOne)
.commit();
//把当前显示的fragment记录下来
currentFragment = fgThree;
}
}
}else{ //正常启动时调用
fgOne = new OneFragment();
fgTwo = new TwoFragment();
fgThree = new ThreeFragment();
showFragment(fgOne);
}
OK,这样就可以了,我们通过保存当前显示的fragment的类名,当我们在第二个fragment页面时后台,等到“内存重启”后返回该app时,就根据之前保存的类名来判断加载指定的fragment,而且,重叠的问题也解决了!
当然,有网友问了,如果fragment比较多,那么多if多麻烦,是啊,上面的代码主要就是让大家理解一下思路,具体开发不建议那么写,下面,就说一下上面的代码该如何精简吧:
创建一个List用来存所有的fragment,给fragment设置tag可以使用当前的索引index,然后在onSaveInstanceState
中保存当前的索引,在恢复时通过索引来找到fragment
//当前显示的fragment
private static final String CURRENT_FRAGMENT = "STATE_FRAGMENT_SHOW";
private TextView tvone;
private TextView tvtwo;
private TextView tvthree;
private FragmentManager fragmentManager;
private Fragment currentFragment = new Fragment();
private List<Fragment> fragments = new ArrayList<>();
private int currentIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.tvthree = (TextView) findViewById(R.id.tv_three);
this.tvtwo = (TextView) findViewById(R.id.tv_two);
this.tvone = (TextView) findViewById(R.id.tv_one);
fragmentManager = getSupportFragmentManager();
tvthree.setOnClickListener(this);
tvtwo.setOnClickListener(this);
tvone.setOnClickListener(this);
if (savedInstanceState != null) { // “内存重启”时调用
//获取“内存重启”时保存的索引下标
currentIndex = savedInstanceState.getInt(CURRENT_FRAGMENT,0);
//注意,添加顺序要跟下面添加的顺序一样!!!!
fragments.removeAll(fragments);
fragments.add(fragmentManager.findFragmentByTag(0+""));
fragments.add(fragmentManager.findFragmentByTag(1+""));
fragments.add(fragmentManager.findFragmentByTag(2+""));
//恢复fragment页面
restoreFragment();
}else{ //正常启动时调用
fragments.add(new OneFragment());
fragments.add(new TwoFragment());
fragments.add(new ThreeFragment());
showFragment();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
//“内存重启”时保存当前的fragment名字
outState.putInt(CURRENT_FRAGMENT,currentIndex);
super.onSaveInstanceState(outState);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_one:
currentIndex = 0;
break;
case R.id.tv_two:
currentIndex = 1;
break;
case R.id.tv_three:
currentIndex = 2;
break;
}
showFragment();
}
/**
* 使用show() hide()切换页面
* 显示fragment
*/
private void showFragment(){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前没有添加过
if(!fragments.get(currentIndex).isAdded()){
transaction
.hide(currentFragment)
.add(R.id.content,fragments.get(currentIndex),""+currentIndex); //第三个参数为添加当前的fragment时绑定一个tag
}else{
transaction
.hide(currentFragment)
.show(fragments.get(currentIndex));
}
currentFragment = fragments.get(currentIndex);
transaction.commit();
}
/**
* 恢复fragment
*/
private void restoreFragment(){
FragmentTransaction mBeginTreansaction = fragmentManager.beginTransaction();
for (int i = 0; i < fragments.size(); i++) {
if(i == currentIndex){
mBeginTreansaction.show(fragments.get(i));
}else{
mBeginTreansaction.hide(fragments.get(i));
}
}
mBeginTreansaction.commit();
//把当前显示的fragment记录下来
currentFragment = fragments.get(currentIndex);
}