β

Exchange Fragment in FragmentTabHost/替换FragmentTab

Kyle's blog 249 阅读
Spread this post. Share on Facebook Share on Google+ Tweet about this on Twitter Share on LinkedIn Share on Tumblr

因为Android App经常要做成iOS的样子,FragmentTabHost也算是比较常用的控件了(不考虑Google打脸的新TabHost控件)。在使用FragmentTabHost时,我们可能会遇到这种需求,就是将某个Tab进行替换,比如“我”这个Tab,如果是未登录状态就需要切换成未登录状态的页面。

本文就讲一下如何实现FragmentTabHost中对Fragment进行替换。先看下效果:

ExchangeFragment

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);
	}
}
  1. 使用mTabHost.clearAllTabs()方法清空Tabs。
  2. 使用反射方法将FragmentTabHost的mTabs字段和mLastTab字段置空。
  3. 使用FragmentManager移除掉需要被替换的Fragment。
  4. 注意: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);
    }

回到上面说的,FragmentTransition是异步的,所以Fragment还没有被Detach,一个Fragment踩到两只FragmentTransition的船上,这船说翻就翻啊。其实是Fragment的状态和Fragment实际的状态会出现不一致的情况,导致Fragment无法Detach,也无法attach。

那不执行第三个操作会怎样?会导致Fragment没有被移除,就出现Fragment重叠的情况了,代码注释掉感受下吧。

对于,项目源码在 Github 上可以找到。

作者:Kyle's blog
A new start.
原文地址:Exchange Fragment in FragmentTabHost/替换FragmentTab, 感谢原作者分享。