写在开端
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在使用过程中确实会存在很多坑,暂时记录到这里,以后再补充.