那些年用过的Fragment-------fragment重叠
写在开端
Fragment的重叠是一个老生常谈的话题了,网上基于该问题出现的原因和解决办法有很多,谨以此文记录笔者的心得体会,也为日后的需要做一个笔记,以方便查阅.
一 Frament重叠出现的场景和原因
1.1 重叠现象问题重现
app开发中最常见的场景:底部四个tab,切换显示不同的fragment.
1) 打开手机设置------> 开发者选项------>打开"不保留活动"(模拟Activity因为内存紧张或者别的原因导致的回收)
2) 点击四个tab,然后将app切换到后台,再点击打开,发现fragment重叠
图1
图2
从上面两个图可以清晰的看出,同样的操作却导致了不同的重叠现象,这是什么原因呢,请往下看.
1.2 Fragment重叠出现的原因
从1.1的两张图我们可以很清晰的看出来,同样的操作,同样的代码,却导致了不同的重叠现象,这是什么原因呢?答案是我用了不同的support-libraey包,第一张图我用的support包是25.3.1 第二张图用的是23.0.0
当support-library包的版本低于24.0.0的时候FragmentState的成员变量如下:
当support-library包是24.0.0以上的时候,Fragmentstate的成员变量如下:
从这两张图,大家可以很明显的看出来,FragmentState相差了一个boolean值的mHidden,那这个变量到底是干什么用的呢?没错,这个变量就是用来标记Fragment的视图信息的(也就是Fragment是否是show状态).在宿主Activity因为内存吃紧,屏幕旋转或者其他的原因被回收的时候会调用自身的onSaveInstanceState()方法,用来保存Activity的现场信息,比如:播放进度,下载进度,当然也包含Fragment堆栈信息,但是可惜的是当support-library的版本低于24.0.0的时候,宿主Activity在保存Fragment相关信息的时候并不会保存其视图信息,又因为Fragment默认是显示的,所以在Activity重启的时候,Activity里面所有的Fragment都会显示,这就会出现2.1中图2的重叠现象,support-library版本在24.0.0以后,谷歌修复了此bug,在Activity回收的时候,同时保存了Fragment的视图信息,那为什么还会出现重叠现象呢?原因是:宿主Activity在重启时,会恢复所有的Fragment信息(包括视图信息,甚至是之前传递到Fragment中的的bundle所夹带的信息),此时其实宿主Activity上已经show了一个Activity被回收之前所显示的那个Fragment,但是别忘了,宿主Activity在重启的时候,生命周期是会重走的,其中的逻辑当然也会重走,一般我们在进入Activity页面的时候都会默认显示第一个Fragment,又因为Fragment背景默认是透明的,所以在界面上,我们会看到两个Fragment重叠,也就是2.1的第一张图所显示的那样.
分析到此,我想大家应该明白Fragment之所以会重叠显示,就是因为宿主Activity在重启的时候,没有正确的show和hide相关的Fragment,解决问题的关键也就在于,当我们进入宿主Activity的时候,在默认显示Fragment之前,首先hide掉堆栈中所有的Fragment,然后再显示要显示的那个Fragment.
二 解决办法
2.1 掩耳盗铃式解决办法
上面我们提到,我们之所以可以看到Fragment的重叠是一个原因是Fragment的背景默认是透明的,当Activity上同时显示两个或者两个以上的Fragment的时候,我们就会很明显的看到重叠现象,既然如此,当我们给每一个Fragment添加一个不透明的背景的时候,我们就不会看到重叠了(因为Fragment的背景颜色覆盖掉了重叠显示的Fragment),这种方法其实并没有解决重叠现象,只是用了一个障眼法让用户看不到底层重叠的Fragment.
2.2 暴力解决方案
注释掉onSaveInstanceState()方法中的super,在宿主Activity被系统回收的时候,不保存Activity的任何现场信息,这样Activity在重启的时候,就不会恢复Fragment,会重走Activity的生命周期和逻辑,默认显示第一个Fragment.该方法笔者并不推荐,因为这种方法在Activity被系统回收的时候并不会保存任何信息,这个代价有点大.
@Override protected void onSaveInstanceState(Bundle outState) { // super.onSaveInstanceState(outState); }
2.3 手动保存Fragment视图信息状态
注意:改解决方式是基于Support-library版本是24.0.0以上
1) 在宿主Activity的onSaveInstanceState方法中,保存当前显示的Fragment的标记
2) 在Activity的OnCreate方法中,当savedInstanceState不为空的时候,得到保存的Fragment标记信息
3) 在Activity显示默认的Fragment之前,hide调Fragment标记所指示的那个Fragment
具体代码如下:
package com.lily.my_viewpagertab; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.TextView; import com.lily.my_viewpagertab.fragment.FourFragment; import com.lily.my_viewpagertab.fragment.OneFragment; import com.lily.my_viewpagertab.fragment.ThreeFragment; import com.lily.my_viewpagertab.fragment.TwoFragment; import java.util.ArrayList; import java.util.List; public class TwoTabActivity extends AppCompatActivity { private String TAG="TwoTabActivity"; TextView tabTextOne; TextView tabTextTwo; TextView tabTextThree; TextView tabTextFour; Fragment oneFragment; Fragment twoFragment; Fragment threeFragment; Fragment fourFragment; FragmentManager manager; FragmentTransaction transaction; List<Fragment> fragmentList=new ArrayList<>(); String currentFragmentFlag=null;// 记录当前显示的Fragment的下标 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_two_tab); initView(); manager=getSupportFragmentManager(); if(savedInstanceState!=null){ // 得到Activity被系统回收前显示的Fragment的标记 currentFragmentFlag=savedInstanceState.getString("flag"); // 恢复Activity的Fragment,防止Fragment被多次创建 // Activity在恢复现场的时候,也会恢复Fragment栈,各个Fragment的生命周期会重走 oneFragment=manager.findFragmentByTag("one"); twoFragment=manager.findFragmentByTag("two"); threeFragment=manager.findFragmentByTag("three"); fourFragment=manager.findFragmentByTag("four"); if(oneFragment!=null){ fragmentList.add(oneFragment); } if(twoFragment!=null){ fragmentList.add(twoFragment); } if(threeFragment!=null){ fragmentList.add(threeFragment); } if(fourFragment!=null){ fragmentList.add(fourFragment); } } initFragment(); } private void initView() { tabTextOne= (TextView) findViewById(R.id.tab01); tabTextTwo= (TextView) findViewById(R.id.tab02); tabTextThree= (TextView) findViewById(R.id.tab03); tabTextFour= (TextView) findViewById(R.id.tab04); } private void initFragment() { transaction=manager.beginTransaction(); if(currentFragmentFlag!=null){ // 隐藏掉Activity被系统回收前显示的那个Fragment if("one".equals(currentFragmentFlag)){ transaction.hide(oneFragment); }else if("two".equals(currentFragmentFlag)){ transaction.hide(twoFragment); }else if("three".equals(currentFragmentFlag)){ transaction.hide(threeFragment); }else if("four".equals(currentFragmentFlag)){ transaction.hide(fourFragment); } } // 默认显示第一个Fragment if(oneFragment==null){ oneFragment=new OneFragment(); fragmentList.add(oneFragment); } // 如何OneFragment没有加入到栈中,证明改Fragment是首次加载,直接add,否则show if(!oneFragment.isAdded()){ transaction.add(R.id.content,oneFragment,"one"); }else { transaction.show(oneFragment); } transaction.commit(); currentFragmentFlag="one"; tabTextOne.setTextColor(getResources().getColor(R.color.red)); } public void tabClick(View view) { transaction=manager.beginTransaction(); switch (view.getId()) { case R.id.tab01: if(oneFragment==null){ oneFragment=new OneFragment(); fragmentList.add(oneFragment); } if(!oneFragment.isAdded()){ transaction.add(R.id.content,oneFragment,"one"); } dealTab(oneFragment); break; case R.id.tab02: if(twoFragment==null){ twoFragment=new TwoFragment(); fragmentList.add(twoFragment); } if(!twoFragment.isAdded()){ transaction.add(R.id.content,twoFragment,"two"); } dealTab(twoFragment); break; case R.id.tab03: if(threeFragment==null){ threeFragment=new ThreeFragment(); fragmentList.add(threeFragment); } if(!threeFragment.isAdded()){ transaction.add(R.id.content,threeFragment,"three"); } dealTab(threeFragment); break; case R.id.tab04: if(fourFragment==null){ fourFragment=new FourFragment(); fragmentList.add(fourFragment); } if(!fourFragment.isAdded()){ transaction.add(R.id.content,fourFragment,"four"); } dealTab(fourFragment); break; } transaction.commit(); } private void dealTab(Fragment selectFragment) { if(selectFragment!=null){ // 先隐藏所有的fragment,然后show要显示的fragment for (int i = 0; i < fragmentList.size(); i++) { transaction.hide(fragmentList.get(i)); } // show要显示的Fragment transaction.show(selectFragment); if(selectFragment instanceof OneFragment){ currentFragmentFlag="one"; dealTabText(tabTextOne); return; } if(selectFragment instanceof TwoFragment){ currentFragmentFlag="two"; dealTabText(tabTextTwo); return; } if(selectFragment instanceof ThreeFragment){ currentFragmentFlag="three"; dealTabText(tabTextThree); return; } if(selectFragment instanceof FourFragment){ currentFragmentFlag="four"; dealTabText(tabTextFour); return; } } } private void dealTabText(TextView selectTextTab) { List<TextView> textViewList=new ArrayList<>(); textViewList.add(tabTextOne); textViewList.add(tabTextTwo); textViewList.add(tabTextThree); textViewList.add(tabTextFour); if(selectTextTab!=null){ for (int i = 0; i < textViewList.size(); i++) { textViewList.get(i).setTextColor(getResources().getColor(R.color.blue)); } selectTextTab.setTextColor(getResources().getColor(R.color.red)); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 标记宿主Activity被回收前,所显示的Fragment的信息 outState.putString("flag",currentFragmentFlag); } }
效果图:
注意:如果你使用的Support-library的版本低于24.0.0,请先hide调堆栈中所有的Fragment,然后再按业务逻辑show你要显示的那个Fragment
写在最后
由于笔者能力有限,文章中若有不足之处,还请小伙伴们指出,笔者必定虚心接受.
Fragment在使用过程中确实会存在很多坑,暂时记录到这里,以后再补充.