防止Fragment重叠的封装

作者: 6db2cd5e23ce | 来源:发表于2016-02-15 16:05 被阅读1005次
    注意:下面的内容已经过时,只留作备份。已经有更好的办法了,请看这里: 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了。

    相关文章

      网友评论

      • 陆地蛟龙:多谢楼主分享。
      • Hidetag:来给老赵满上。。这东西小公司或许不会重视,但是有些大公司或者项目经理是“处女”座的时候,可能会纠结,mark备用

      本文标题:防止Fragment重叠的封装

      本文链接:https://www.haomeiwen.com/subject/vrjpkttx.html