防止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 的切换<br/>
 * 直接调用 {@link #switchFragment(int, BaseFragment, boolean)} 即可
 */
public abstract class BaseFragmentActivity extends FragmentActivity{

    /**
     * 所有Fragment tag的集合
     * key: container id
     */
    private Set<String> mFragmentTags;
    /**
     * 当前Fragment的tag
     * key: containerId
     */
    private SparseArray<String> 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<String> tagSet = getOrInitFragmentTags();
            tagSet.clear();
            Collections.addAll(tagSet, allFragmentTags);
            ArrayList<BaseFragmentActivitySaveEntity.IntKeyStringValue> keyValueList = entity.getKeyValue();
            SparseArray<String> 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<String> tagSet = getOrInitFragmentTags();
        String[] allTags = new String[tagSet.size()];
        Iterator<String> it = tagSet.iterator();
        for (int i = 0; it.hasNext(); i++) {
            allTags[i] = it.next();
        }

        ArrayList<BaseFragmentActivitySaveEntity.IntKeyStringValue> keyValueList = new ArrayList<>();
        SparseArray<String> 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<String> set = getOrInitFragmentTags();
        set.add(getFragmentSignature(fragment));
    }

    private void setCurrFragmentTag(int containerId, BaseFragment fragment) {
        SparseArray<String> fragmentTags = getOrInitCurrFragmentTags();
        fragmentTags.put(containerId, getFragmentSignature(fragment));
    }

    private void updateCurrFragmentTag() {
        if (mPrimaryContainer == 0) return;
        SparseArray<String> 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<String> getOrInitFragmentTags() {
        if (mFragmentTags == null) mFragmentTags = new HashSet<>();
        return mFragmentTags;
    }

    private SparseArray<String> 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<IntKeyStringValue> keyValue;
    private int containerId;
    private String noStackTag;

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

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

    public ArrayList<IntKeyStringValue> getKeyValue() {
        return keyValue;
    }

    public void setKeyValue(ArrayList<IntKeyStringValue> 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<BaseFragmentActivitySaveEntity> CREATOR = new Parcelable.Creator<BaseFragmentActivitySaveEntity>() {
        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<IntKeyStringValue> CREATOR = new Parcelable.Creator<IntKeyStringValue>() {
            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了。

    原文作者:Hideeee
    原文地址: https://www.jianshu.com/p/560fd5895cd7
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞