因为Android App经常要做成iOS的样子,FragmentTabHost也算是比较常用的控件了(不考虑Google打脸的新TabHost控件)。在使用FragmentTabHost时,我们可能会遇到这种需求,就是将某个Tab进行替换,比如“我”这个Tab,如果是未登录状态就需要切换成未登录状态的页面。
本文就讲一下如何实现FragmentTabHost中对Fragment进行替换。先看下效果:
]
FragmentTabHost继承自TabHost,使用TabHost的切换逻辑,又通过FragmentManager封装了对Fragment切换的操作。FragmentTabHost在实现时通过使用TabInfo 缓存Fragment,所以我们在替换Fragment的时候,要考虑到复用的问题。
思路不难,清空FragmentTabHost,重建Tabs。因为有缓存,所以清空的时候麻烦一点。核心代码如下:
public void resetTabs(String oldFragmentTag) { if (mTabHost != null) { int currentTab = mTabHost.getCurrentTab(); mTabHost.clearAllTabs(); //noinspection TryWithIdenticalCatches try { Field mTabs = mTabHost.getClass().getDeclaredField("mTabs"); mTabs.setAccessible(true); Object o = mTabs.get(mTabHost); if (o instanceof List) { ((List) o).clear(); } Field mLastTab = mTabHost.getClass().getDeclaredField("mLastTab"); mLastTab.setAccessible(true); mLastTab.set(mTabHost, null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } if (!TextUtils.isEmpty(oldFragmentTag)) { try { Fragment f = getSupportFragmentManager().findFragmentByTag(oldFragmentTag); if (f != null) { getSupportFragmentManager().beginTransaction().remove(f).commitAllowingStateLoss(); } } catch (Exception e) { e.printStackTrace(); } } createTabs(); mTabHost.setCurrentTab(currentTab); } }
- 使用mTabHost.clearAllTabs()方法清空Tabs。
- 使用反射方法将FragmentTabHost的mTabs字段和mLastTab字段置空。
- 使用FragmentManager移除掉需要被替换的Fragment。
- 注意:FragmentTabHost中的Tag一定要是唯一的。
如果只进行第三个操作,然后调用createTabs()重建Tabs,在切换成原始Fragment的时候就会出现页面空白的Bug。原因是,FragmentManager操作Fragment的FragmentTransaction操作是异步的,调用FragmentTransaction的commit方法时,是将一系列操作添加到了队列中,并没有立刻执行。
而此时如果执行FragmentTabHost的addTab方法,FragmentTabHost会使用tag参数在FragmentManager中查找Fragment,如果存在并且没有被Detach,就会将其detach掉。FragmentTabHost源码:
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
if (mAttached) {
// If we are already attached to the window, then check to make
// sure this tab's fragment is inactive if it exists. This shouldn't
// normally happen.
info.fragment = mFragmentManager.findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
}
mTabs.add(info);
addTab(tabSpec);
}</pre>
回到上面说的,FragmentTransition是异步的,所以Fragment还没有被Detach,一个Fragment踩到两只FragmentTransition的船上,这船说翻就翻啊。其实是Fragment的状态和Fragment实际的状态会出现不一致的情况,导致Fragment无法Detach,也无法attach。
那不执行第三个操作会怎样?会导致Fragment没有被移除,就出现Fragment重叠的情况了,代码注释掉感受下吧。
对于,项目源码在Github上可以找到。