防止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
了。