今天我们就来谈谈Fragment在Tab切换中的状态变化等。
这里我们就拿QQ来分析
QQ主页包含3个模块:消息、联系人、动态。消息模块又包含了两个子模块:消息和电话。
这种使用Fragment来实现是再好不过的了。
首先底部的我们使用FragmentTabHost即可,这里我们对系统的这个控件做了简单的修改。系统的这个控件在切换tab的时候是会detach 当前的Fragment, 也就是销毁当前Fragment的视图。这样就会导致每次切换tab的时候都会重新走onCreateView,重新创建Fragment view。这样我们之前的状态就会丢失,这当然不是我们所想要的。
private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
FragmentTabHost.TabInfo newTab = null;
for (int i = 0; i < mTabs.size(); i++) {
FragmentTabHost.TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
if (newTab == null) {
throw new IllegalStateException("No tab known for tag " + tabId);
if (mLastTab != newTab) {
if (ft == null) {
ft = mFragmentManager.beginTransaction();
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mContext, newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
ft.attach(newTab.fragment);
mLastTab = newTab;
return ft;
git.oschina.net/jaaksi/Base…
这里我们只做了很少的改动。主要是把detach和attach相关的代码改为hide和show。这样Fragment加载一次后就不会再重新加载了,我们的状态也不会丢失。
这里还封装了一个BaseTabActivity基类
git.oschina.net/jaaksi/Base…
当然,如果你不想用FragmentTabHost,我推荐你使用另外一个强大的Tab库。
github.com/H07000223/F…
它的强大我这里就罗嗦了,感兴趣的可以看看这个库。这位大神还有另外一个强大的库RoundView.
使用FragmentTabHost,我们就可以很简单的实现底部的3个tab。再去分析消息模块的子模块。这里我们就可以使用上面提到的FlycoTabLayout库来实现(它在切换的时候也是使用的hide, show的方式),当然你也可以手动去实现。我是个不喜欢重复造轮子的人。
1.这里要用到Fragment嵌套子Fragment。要注意在Fragment中嵌套Fragment要使用getChildFragmentManager()来获取FragmentManager。这里TabLayout库就不能用了。我改了一下它的setTabData()方法,直接将FragmentManager传过去,这样就不用考虑是否是子Fragment了。
2.还有一点,FragmentTabHost(我们修改的)只会加载一个Fragment,当切到指定tab时,才会去加载其他的,而把之前的hide。而FlycoTabLayout库则一开始会把所有的Fragment都加载进来,然后hide所有,然后再show指定tab的。如果你不想要这样的效果,你可以很简单的去修改这个库。
总之,实现这样的功能很简单,这个并不是我们今天要说的重点。我们要分析的是在tab切换时,对应Fragment的状态变化。
第一次创建主页Activity时处于联系人Fragment,然后当我们切换到消息Fragment,msg开始创建,这个过程消息Fragment和它的两个子Fragment都经历了什么?
切换消息的两个子Fragment,他们的状态又是如何变化的?
onResume又会对这些Fragment有什么影响?
事实上QQ并不是在初始化的时候只加载一个Fragment,在切换时才会去加载其他Fragment,这里我们只是拿QQ来描述我们的使用场景。
为了更直接的分析上面的几个问题,我们来分析几个方法:
isResume()
isHidden()
isVisible()
onResume()
onHiddenChanged()
我们今天主要也就是搞清楚在切换tab及onResume时Fragment的这些回调及状态的变化。下面先来简单解释一下这些方法。
onResume()不用多说,和Activity的onResume是对应的。
isResume()也很简单,就是Fragment是否处于Resume状态,即onResume()之后就为ture,onPause()之后为false,这里不做多说。
* Called when the hidden state (as returned by {@link
* the fragment has changed. Fragments start out not hidden; this will
* be called whenever the fragment changes state from that.
* @param hidden True if the fragment is now hidden, false otherwise.
public void onHiddenChanged(boolean hidden) {
onHiddenChanged 方法是在Fragment的hidden state发生改变的回调的方法。这个回调的时机是我们主动调用hide(), show()方法。
需要说明的是Fragment在初始化的时候并不会回调onHiddenChanged()方法。
/**
* Return true if the fragment has been hidden. By default fragments
* are shown. You can find out about changes to this state with
* {@link
* to other states -- that is, to be visible to the user, a fragment
* must be both started and not hidden.
final public boolean isHidden() {
return mHidden;
isHidden()就是返回hidden state,我们可以通过onHiddenChanged()回调来监听Fragment 这个状态的变化。这个回调的参数其实就是当前的hidden state.
默认情况下,add之后的Fragment是处于shown状态的。
* Return true if the fragment is currently visible to the user. This means
* it: (1) has been added, (2) has its view attached to the window, and
* (3) is not hidden.
final public boolean isVisible() {
return isAdded()
&& !isHidden()
&& mView != null
&& mView.getWindowToken() != null
&& mView.getVisibility() == View.VISIBLE;
这里着重说一下isVisible()这个方法。
Return true if the fragment is currently visible to the user。
看官方注释,很多人理解为这个返回值就是指Fragment是否对用户可见。事实上这么说是不完全正确的。
我们分析一下,这个方法的实现,isAdd()是否添加,!isHidden()是否隐藏,后面的表示Fragment依附的容器view是否visible,该view是否依附在window中。
对于普通的Fragment而言,这么理解是对的。但是对于嵌套在Fragment中的子Fragment,就不对了。
如果当前嵌套中的子Fragment
isVisible()=true,此时调用父Fragment的hide()方法,那么对父Fragment而言,isHidden()返会ture,isVisible()返回false。而对于子Fragment 并没有调用hide(),show()方法,父Fragment的hide,show对它并没有任何影响,isVisible()依然是true的。但事实上,因为父Fragment是不可见的了,所以自然而然子Fragment也是不可见的了。
所以我们可以这么改造一下这个方法。真正意义上的可见。