Fragment在Android开发中占据着不可替代的作用。
举一些常见的应用场景:

  • 各种tab切换页面
  • 解耦Activity
  • 业务复用

今天我们就来谈谈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;
}

http://git.oschina.net/jaaksi/BaseLib/blob/master/src/main/java/org/an/ku/base/FragmentTabHost.java
这里我们只做了很少的改动。主要是把detach和attach相关的代码改为hide和show。这样Fragment加载一次后就不会再重新加载了,我们的状态也不会丢失。
这里还封装了一个BaseTabActivity基类
http://git.oschina.net/jaaksi/BaseLib/blob/master/src/main/java/org/an/ku/base/BaseTabsActivity.java
当然,如果你不想用FragmentTabHost,我推荐你使用另外一个强大的Tab库。
https://github.com/H07000223/FlycoTabLayout/blob/master/README_CN.md
它的强大我这里就罗嗦了,感兴趣的可以看看这个库。这位大神还有另外一个强大的库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 #isHidden()} of* 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 #onHiddenChanged}.  Note that the hidden state is orthogonal* 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也是不可见的了。

所以我们可以这么改造一下这个方法。真正意义上的可见。

/*** 是否真正的对用户可见* @return*/
public boolean isRealVisible() {if (getParentFragment() == null) {return isVisible();} else {return isVisible() && getParentFragment().isVisible();}
}

解释完这些方法,接下来,我们来分析一个完整的流程中Fragment的状态变化。还拿QQ来描述。


我们分析一这样个场景:
进入主页(默认初始化联系人Fragment,尚且认为其他不会被初始化),然后切换到消息模块,将这个过程定义为过程A。然后再切换到联系人模块,这个过程定义为B。
为了简单的描述,我们记消息模块Fragment 为 MsgF,子消息Fragment为MsgSubF,联系人模块为ContactF。
下面就分析一下A和B过程中都发生了什么:

A过程,切换到消息模块时,MsgF开始创建,子Fragment MsgSubF开始创建。MsgF onResume(),而后MsgSubF onResume().整个过程就是一个简单的初始化过程。
B过程,切回联系人模块,MsgF被hide,回调onHiddenChanged()方法,isVisible()=false。但正如前面说到的,子Fragment MsgSubF并不会回调onHiddenChanged(),isVisible()依然是true,但是父Fragment不可见了,子Fragment也就不可见了。

分析完上面的场景,我们来分析一个开发中的应用。

假设我们要在很多Activity页面做某个操作后回到消息列表时需要刷新子Fragment MsgSubF页面。

首先多个Activity,如果我们采用startActivityForResult就不是很方便了。两个原因,子Fragment是不能接收到onActivityResult回调的(非嵌套可以)。第二个原因,即使是非嵌套,可以接收到onActivityResult回调,也不推荐使用。因为如果有很多跳转时,各种requestCode,resultCode,就会显得比较乱,难以维护。对于这种统一行为的操作,建议使用EventBus。在触发的地方发送一个事件,在MsgSubF(需要处理的地方)中处理。
然而eventbus发送之后立刻就会收到,我们是希望,在MsgSubF页面对用户可见时才去刷新。那么该怎么处理呢?实际上我们可以在接收到event的时候,设置一个flag,用于标识是否需要刷新。在Fragment可见的时候再去做刷新操作。

秉着这个思路,我们去分析。这里要考虑回到主页时是否处于消息模块(确切的说子Fragment是否是真的对用户可见的)。回到主页时,Fragment和子Fragment都会回调onResume().所以如果处于消息模块,就很简单了,直接在MsgSubF中的onResume方法中,根据flag判断是否需要刷新,如果需要,就去执行,刷新之后重置flag。

我们来重点分析一下,另外一种情况。

回到主页时,并未处于MsgF,而MsgSubF isVisible()是true的。当回到主页时,回调onResume,但是MsgSubF是不可见的,所以此时不应该去处理刷新。应该在切换到消息模块,MsgSubF可见的时候,再去执行刷新。然而不幸的是,切换tab时,只会回调父Fragment的onHiddenChanged()方法,子Fragment并不会回调。这就比较尴尬了。我们是没有办法直接通过系统的回调方法来处理了。
既然父Fragment会回调,而父Fragment又可以持有子Fragment的引用。那么我就可以在父Fragment的回调中去主动调用子Fragment的onHiddenChanged方法。

@Override public void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);// fixme 由于该方法只会在hide,show的时候回调,导致切换父fragment tab时,子Fragment不会回调此方法,如果需要子Fragment也回调,就手动调用for (int i = 0; i < mFragmentList.size(); i++) {Fragment fragment = mFragmentList.get(i);fragment.onHiddenChanged(fragment.isHidden());}
}

你也可以定义一个接口,让你的子Fragment实现这个接口,然后在父onHiddenChanged()回调中去回调这个接口。

public interface OnSupperHiddenChangedListener {void onSupperHiddenChanged(boolean hidden);
}

这么一来,我们就可以实现在子Fragment真正可见的时候去刷新了。

作者:笨小孩丶
链接:https://www.jianshu.com/p/b985d3935fbd
来源:简书

谈谈Fragment的用法之Fragment实现Tab切换中的那些事相关推荐

  1. vue中怎么清空tab选项卡的缓存_vuejs 内置组件component实现tab切换懒加载和表单输入框内容的清空...

    最近项目中使用了vue2.0 官网脚手架进行了开发.开发中的踩一些坑一直没有来得及整理.本篇文章就vuejs内置组件component的一些使用进行一些总结. 1.使用component 实现tab切 ...

  2. 解决Echarts使用tab切换时只显示第一个tab中图表,其他tab中图表不显示或显示不全问题

    近期项目中使用到了Echarts来在展示图表,两个tab切换页面中都存在图表,页面加载完成后都对所有图表进行了初始化和绘制,然而在tab切换中出现了如下动图中的问题: 说明:图中可以看到,第一个tab ...

  3. 安卓APP_ Fragment(1)—— Fragment概念、基础用法、动态变换、管理栈

    摘自:安卓APP_ Fragment(1)-- Fragment概念.基础用法.动态变换.管理栈 作者:丶PURSUING 发布时间: 2021-04-15 23:32:31 网址:https://b ...

  4. android studio 导入包分不分动态静态,详解Android studio 动态fragment的用法

    fragment的使用时Android的基础,它有两种用法,第一个就是静态的fragment.第二个则是动态的fragment. 静态fragment直接在layout创建你想要的fragment的X ...

  5. Android开发之ViewPager+ActionBar+Fragment实现响应式可滑动Tab

    今天我们要实现的这个效果呢,在Android的应用中十分地常见,我们可以看到下面两张图,无论是系统内置的联系人应用,还是AnyView的阅读器应用,我们总能找到这样的影子,当我们滑动屏幕时,Tab可以 ...

  6. Fragment的用法,举例介绍

    Fragment(碎片) 1.碎片是什么 碎片(Fragment)是一种可以嵌入在活动当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用的非常广泛.虽然碎片对你来说应该是个全 ...

  7. ViewPager+Fragment实现支持左右滑动的Tab

    主要思想:顶部标题栏top.xml,中间ViewPager(4个Fragment),底部导航 top.xml和bottom.xml在我之前的两个随笔里有,此处不再赘述. activity_main.x ...

  8. 史上最全Fragment介绍,包括fragment的定义,生命周期,用法

    一.为什么要使用Fragment 1.当我们需要动态的多界面切换的时候,就需要将UI元素和Activity融合成一个模块.在2.3中我们一般通过各种Activity中进行跳转来实现多界面的跳转和单个界 ...

  9. Fragment详解之五——Fragment间参数传递

    相关文章: 1.<Fragment详解之一--概述> 2.<Fragment详解之二--基本使用方法> 3.<Fragment详解之三--管理Fragment(1)> ...

  10. Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复

    Android中的状态保存和恢复 Android中的状态保存和恢复, 包括Activity和Fragment以及其中View的状态处理. Activity的状态除了其中的View和Fragment的状 ...

最新文章

  1. cnetos7 vncserver安装与配置
  2. 倒计时几秒_和平精英:倒计时0秒时进圈会不会被淘汰?主播展示极限卡圈
  3. markdown 自用笔记
  4. 2021-09-211547G - How Many Paths?
  5. 理解和使用SQL Server中的并行
  6. java里加载是什么意思_Java 类加载机制详解
  7. Hive:用Java代码通过JDBC连接Hiveserver
  8. MSU 出品的 H.264 编码器比较 (2011.5)
  9. P2P 网络核心技术:Gossip 协议
  10. 讲座:数据库项目生命周期中的优化 讲稿下载
  11. win7用计算机名共享打印机,WIN7共享打印机 可以同时多少台电脑共享一台打印机-win7一键共享打印机,win7一键共享工具软件...
  12. VSCode下载安装教程(Windows 10)
  13. 设计一个算法,通过一趟遍历确定长度为n的但链表的中值的最大的节点
  14. ppt批量转为pptx格式。
  15. 程序员凌晨3点不回家
  16. Vue vben admin - 新鲜出炉的高颜值管理后台UI框架,基于 Vue3 和 Ant Design Vue
  17. SPI Flash/Nor Flash/Nand Flash
  18. 行人车辆检测与计数系统(Python+YOLOv5深度学习模型+清新界面)
  19. 无光驱安装原版 windows server2008,win7 的方法,64位的
  20. ubuntu 18.04 安装 搜狗拼音输入法只有中文标点,没有文字

热门文章

  1. 我国数学如何面对西方数学?
  2. 思维导图一定要用计算机来完成吗,程序猿不仅可以用电脑敲代码,还可以用来制作思维导图...
  3. vue项目如何做到每30秒刷新1次接口?
  4. 女神:今天我3倍工资,放假半天 有法可依,我...
  5. 【推荐】样本/数据一致性检验的方法
  6. 计算机键盘上的tab键是什么键,键盘Tab键有什么作用?
  7. 支付宝小程序获取用户手机号php,小程序登录、获取用户信息、手机号
  8. linux命令之拒绝服务-关机重启shutdown-init0-6删库(滑稽脸)
  9. 3DMAX零基础图文教程学习
  10. 3207: 花神的嘲讽计划Ⅰ