写在开端

Fragment的重叠是一个老生常谈的话题了,网上基于该问题出现的原因和解决办法有很多,谨以此文记录笔者的心得体会,也为日后的需要做一个笔记,以方便查阅.

一 Frament重叠出现的场景和原因

1.1 重叠现象问题重现

app开发中最常见的场景:底部四个tab,切换显示不同的fragment.

1) 打开手机设置------> 开发者选项------>打开"不保留活动"(模拟Activity因为内存紧张或者别的原因导致的回收)

2) 点击四个tab,然后将app切换到后台,再点击打开,发现fragment重叠

Android Fragment重叠 fragment重叠原因_ide

                        图1

Android Fragment重叠 fragment重叠原因_Fragment的重叠_02

                            图2

从上面两个图可以清晰的看出,同样的操作却导致了不同的重叠现象,这是什么原因呢,请往下看.

1.2 Fragment重叠出现的原因

从1.1的两张图我们可以很清晰的看出来,同样的操作,同样的代码,却导致了不同的重叠现象,这是什么原因呢?答案是我用了不同的support-libraey包,第一张图我用的support包是25.3.1 第二张图用的是23.0.0

当support-library包的版本低于24.0.0的时候FragmentState的成员变量如下:

Android Fragment重叠 fragment重叠原因_Fragment的重叠_03

当support-library包是24.0.0以上的时候,Fragmentstate的成员变量如下:

Android Fragment重叠 fragment重叠原因_重启_04

从这两张图,大家可以很明显的看出来,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);
    }
}



效果图:

Android Fragment重叠 fragment重叠原因_ide_05

注意:如果你使用的Support-library的版本低于24.0.0,请先hide调堆栈中所有的Fragment,然后再按业务逻辑show你要显示的那个Fragment



写在最后

由于笔者能力有限,文章中若有不足之处,还请小伙伴们指出,笔者必定虚心接受.

Fragment在使用过程中确实会存在很多坑,暂时记录到这里,以后再补充.