防止Fragment重叠的封装

注意:下面的内容已经过时,只留作备份。已经有更好的办法了,请看这里: 9行代码让你App内的Fragment对重叠说再见

前言

FragmentActivity里面可以添加多个Fragment,一般的侧滑菜单界面或者Tab导航都是这样做的。但有一个令人头疼的问题:当切换了很多个Fragment,然后在后台被回收又重新显示出来的时候,Fragment会重叠在一起!

其实这是一个非常严重的问题,但我发现不重视的公司还不少,比如我上一家公司,在发现Fragment重叠的BUG后采取的方案是:把Fragment的背景加上颜色,不让后面的Fragment露出来。

这种自欺欺人的方案,尼玛……

Fragment为什么会重叠呢?

很多人添加Fragment是在onCreate()里面直接替换,其实细心一点可以发现,之前有一个版本的ADT在建立工程的时候会自动生成Fragment,而自动生成的Fragment是这样写的:

if(savedStateInstance == null) {
    //添加Fragment
   ..............
}

也就是说,Android会自动保存Fragment的实例,如果在添加Fragment的时候不加这个if判断,想一下,那么老的Fragment和新的也就重叠在一起了。

还有一个原因,在恢复Fragment的时候hide()状态是会失效的,之前通过hide()隐藏的Fragment也会同时显示出来……

所以知道了原因就好办了,既然老的Fragment不会被回收,那在重启的时候通过tag找到已经存在的Fragment再显示出来就好了。

在代码基本完成之后一测试,发现有一个大坑:有些Fragment是有返回栈的,也就是按了返回键可以返回到上一个Fragment
加上返回栈这个逻辑后,之前的代码产生了BUG,因为无法准确记录当前正在显示的Fragment是哪一个。
这时候就要用到addOnBackStackChangedListener()监听事件了,这样当返回栈状态发生改变就可以监听到,然后通过getBackStackEntryAt()取出返回栈中Fragment的名字。

代码实现

/**
 * 封装了 Fragment 的切换
* 直接调用 {@link #switchFragment(int, BaseFragment, boolean)} 即可 */ public abstract class BaseFragmentActivity extends FragmentActivity{ /** * 所有Fragment tag的集合 * key: container id */ private Set mFragmentTags; /** * 当前Fragment的tag * key: containerId */ private SparseArray mCurrFragmentTags; /** * Fragment可以同时添加多个,这里只保存一个支持返回栈的containerId */ private int mPrimaryContainer; /** * 保存一个未添加入返回栈的Tag; */ private String mNoStackFragmentTag; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportFragmentManager().addOnBackStackChangedListener(this); if (savedInstanceState == null) return; try { BaseFragmentActivitySaveEntity entity = savedInstanceState.getParcelable(BaseFragmentActivitySaveEntity.ENTITY_KEY); mPrimaryContainer = entity.getContainerId(); mNoStackFragmentTag = entity.getNoStackTag(); String[] allFragmentTags = entity.getAllFragmentTags(); Set tagSet = getOrInitFragmentTags(); tagSet.clear(); Collections.addAll(tagSet, allFragmentTags); ArrayList keyValueList = entity.getKeyValue(); SparseArray currTags = getOrInitCurrFragmentTags(); currTags.clear(); for (BaseFragmentActivitySaveEntity.IntKeyStringValue keyValue : keyValueList) { currTags.put(keyValue.getKey(), keyValue.getTag()); } FragmentManager fManager = getSupportFragmentManager(); FragmentTransaction ft = fManager.beginTransaction(); for (String tag : tagSet) { hideFragmentFromTagNoCommit(fManager, ft, tag); } for (BaseFragmentActivitySaveEntity.IntKeyStringValue keyValue : keyValueList) { Fragment fragment = fManager.findFragmentByTag(keyValue.getTag()); if (fragment == null || !(fragment instanceof BaseFragment)) continue; showOrAddFragmentNoCommit(fManager, ft, keyValue, (BaseFragment) fragment, false); } ft.commitAllowingStateLoss(); } catch (Exception e) { e.printStackTrace(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Set tagSet = getOrInitFragmentTags(); String[] allTags = new String[tagSet.size()]; Iterator it = tagSet.iterator(); for (int i = 0; it.hasNext(); i++) { allTags[i] = it.next(); } ArrayList keyValueList = new ArrayList<>(); SparseArray currFragmentTags = getOrInitCurrFragmentTags(); for (int i = 0; i < currFragmentTags.size(); i++) { BaseFragmentActivitySaveEntity.IntKeyStringValue keyValue = new BaseFragmentActivitySaveEntity.IntKeyStringValue(); keyValue.setKey(currFragmentTags.keyAt(i)); keyValue.setTag(currFragmentTags.valueAt(i)); keyValueList.add(keyValue); } BaseFragmentActivitySaveEntity entity = new BaseFragmentActivitySaveEntity(); entity.setAllFragmentTags(allTags); entity.setKeyValue(keyValueList); entity.setContainerId(mPrimaryContainer); entity.setNoStackTag(mNoStackFragmentTag); outState.putParcelable(BaseFragmentActivitySaveEntity.ENTITY_KEY, entity); } @Override protected void onDestroy() { super.onDestroy(); getSupportFragmentManager().removeOnBackStackChangedListener(this); } protected void switchFragment(int containerId, BaseFragment fragment, boolean addToBackStack) { addNewFragmentToSet(fragment); String lastFragmentTag = getCurrFragmentTag(containerId); setCurrFragmentTag(containerId, fragment); if (mPrimaryContainer == 0) mPrimaryContainer = containerId; FragmentManager fManager = getSupportFragmentManager(); FragmentTransaction ft = fManager.beginTransaction(); hideFragmentFromTagNoCommit(fManager, ft, lastFragmentTag); showOrAddFragmentNoCommit(fManager, ft, containerId, fragment, addToBackStack); ft.commitAllowingStateLoss(); } @SuppressLint("CommitTransaction") private void showOrAddFragmentNoCommit(FragmentManager fManager, FragmentTransaction ft , int containerId, BaseFragment fragment, boolean addToBackStack) { Fragment lastFragment = fManager.findFragmentByTag(getFragmentSignature(fragment)); if (lastFragment != null) { ft.show(lastFragment); } else { ft.add(containerId, fragment, getFragmentSignature(fragment)); if (addToBackStack) { ft.addToBackStack(getFragmentSignature(fragment)); } else if (TextUtils.isEmpty(mNoStackFragmentTag)) { mNoStackFragmentTag = getFragmentSignature(fragment); } } } @SuppressLint("CommitTransaction") private void showOrAddFragmentNoCommit(FragmentManager fManager, FragmentTransaction ft , BaseFragmentActivitySaveEntity.IntKeyStringValue keyValue, BaseFragment fragment, boolean addToBackStack) { Fragment lastFragment = fManager.findFragmentByTag(keyValue.getTag()); if (lastFragment != null) { ft.show(lastFragment); } else { ft.add(keyValue.getKey(), fragment, keyValue.getTag()); if (addToBackStack) ft.addToBackStack(keyValue.getTag()); } } @SuppressLint("CommitTransaction") private void hideFragmentFromTagNoCommit(FragmentManager fManager, FragmentTransaction ft, String tag) { if (TextUtils.isEmpty(tag)) return; Fragment lastFragment = fManager.findFragmentByTag(tag); if (lastFragment == null) return; ft.hide(lastFragment); } private void addNewFragmentToSet(BaseFragment fragment) { Set set = getOrInitFragmentTags(); set.add(getFragmentSignature(fragment)); } private void setCurrFragmentTag(int containerId, BaseFragment fragment) { SparseArray fragmentTags = getOrInitCurrFragmentTags(); fragmentTags.put(containerId, getFragmentSignature(fragment)); } private void updateCurrFragmentTag() { if (mPrimaryContainer == 0) return; SparseArray fragmentTags = getOrInitCurrFragmentTags(); int count = getSupportFragmentManager().getBackStackEntryCount(); if (count <= 0) { fragmentTags.put(mPrimaryContainer, mNoStackFragmentTag); return; } String name = getSupportFragmentManager().getBackStackEntryAt(count - 1).getName(); fragmentTags.put(mPrimaryContainer, name); } private String getCurrFragmentTag(int containerId) { return getOrInitCurrFragmentTags().get(containerId); } private String getFragmentSignature(BaseFragment fragment) { return fragment.getFragmentSignature(); } private Set getOrInitFragmentTags() { if (mFragmentTags == null) mFragmentTags = new HashSet<>(); return mFragmentTags; } private SparseArray getOrInitCurrFragmentTags() { if (mCurrFragmentTags == null) mCurrFragmentTags = new SparseArray<>(); return mCurrFragmentTags; } @Override public void onBackStackChanged() { updateCurrFragmentTag(); } }
/**
 * 用来恢复Fragment的实体
 */
public class BaseFragmentActivitySaveEntity implements Parcelable {
    public static final String ENTITY_KEY = "BaseFragmentActivitySaveEntity";

    private String[] allFragmentTags;
    private ArrayList keyValue;
    private int containerId;
    private String noStackTag;

    public String[] getAllFragmentTags() {
        return allFragmentTags;
    }

    public void setAllFragmentTags(String[] allFragmentTags) {
        this.allFragmentTags = allFragmentTags;
    }

    public ArrayList getKeyValue() {
        return keyValue;
    }

    public void setKeyValue(ArrayList keyValue) {
        this.keyValue = keyValue;
    }

    public int getContainerId() {
        return containerId;
    }

    public void setContainerId(int containerId) {
        this.containerId = containerId;
    }

    public String getNoStackTag() {
        return noStackTag;
    }

    public void setNoStackTag(String noStackTag) {
        this.noStackTag = noStackTag;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeStringArray(this.allFragmentTags);
        dest.writeTypedList(keyValue);
        dest.writeInt(this.containerId);
        dest.writeString(this.noStackTag);
    }

    public BaseFragmentActivitySaveEntity() {
    }

    protected BaseFragmentActivitySaveEntity(Parcel in) {
        this.allFragmentTags = in.createStringArray();
        this.keyValue = in.createTypedArrayList(IntKeyStringValue.CREATOR);
        this.containerId = in.readInt();
        this.noStackTag = in.readString();
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public BaseFragmentActivitySaveEntity createFromParcel(Parcel source) {
            return new BaseFragmentActivitySaveEntity(source);
        }

        public BaseFragmentActivitySaveEntity[] newArray(int size) {
            return new BaseFragmentActivitySaveEntity[size];
        }
    };


    public static class IntKeyStringValue implements Parcelable {
        private int key;
        private String tag;

        public int getKey() {
            return key;
        }

        public void setKey(int key) {
            this.key = key;
        }

        public String getTag() {
            return tag;
        }

        public void setTag(String tag) {
            this.tag = tag;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(this.key);
            dest.writeString(this.tag);
        }

        public IntKeyStringValue() {
        }

        protected IntKeyStringValue(Parcel in) {
            this.key = in.readInt();
            this.tag = in.readString();
        }

        public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
            public IntKeyStringValue createFromParcel(Parcel source) {
                return new IntKeyStringValue(source);
            }

            public IntKeyStringValue[] newArray(int size) {
                return new IntKeyStringValue[size];
            }
        };
    }

}

这两段代码累死人了,其中BaseFragmentActivity就是对Fragment切换的封装,而BaseFragmentActivitySaveEntity是实现了序列化的实体类,用来保存Fragment的一些状态。

getFragmentSignature()的意思是生成一个标识Fragment的唯一字符串,一般都用getClass().getSimpleName(),这个就看你自己的逻辑了。

使用比较简单:
首先用自己的Activity继承自BaseFragmentActivity,然后直接这样使用:

if (savedInstanceState == null) {
        switchFragment(R.id.container, TestFragment.getInstance(1), true);
   }

最后一个布尔值代表是否加入到返回栈中。

经过测试,在一般情况下,例如:普通的侧边栏、Tab导航、单个容器添加多个Fragment、屏幕旋转等等,是没问题的(自测暂时没问题……)。

代码就只有这两段,复制下来可以直接用,就不发布到Github了。

你可能感兴趣的:(防止Fragment重叠的封装)