相关文章:

1、《Fragment详解之一——概述》
2、《Fragment详解之二——基本使用方法》
3、《Fragment详解之三——管理Fragment(1)》
4、《Fragment详解之四——管理Fragment(2)》
5、《Fragment详解之五——Fragment间参数传递》
6、《Fragment详解之六——如何监听fragment中的回退事件与怎样保存fragment状态》

上一篇,给大家讲了有关Fragment管理的几个函数,即add,replace,remove,这节再讲讲其它函数,然后再给大家看一个系统BUG。

一、hide()、show()

1、基本使用

这两个函数的功能非常简单,

[java] view plaincopy
  1. public FragmentTransaction hide(Fragment fragment);//将指定的fragment隐藏不显示
  2. public FragmentTransaction show(Fragment fragment);//将以前hide()过的fragment显示出来

先看下面的效果图:

  • 首先,依次添加fragment1,fragment2,fragment3
  • 然后点击”frag3 hide”,将fragment3隐藏不显示,所以就显示出来它的下一层fragment2的视图
  • 然后再点击“frag3 show”,将fragment3重新显示出来
  • 然后点击“frag2 hide”按钮,将fragment2隐藏,但是由于fragment3覆盖在fragment2之上,fragment2隐藏之后对fragment3没有任何影响,所以在视图上看不到任何效果。
  • 这时候,我们再点击“hide frag3”,将fragment3隐藏起来,这时候,由于fragment3和fragment2都隐藏了,所以显示的就是fragment1的视图。
  • 最后,点击“frag2 show”将fragment2显示出来

代码如下:
(1)、同样是新建三个fragment,命名为Fragment1,Fragment2,Fragment3,同样是用背景色和文字来区别;
(2)、然后是点击“add frag1”按钮的代码

[java] view plaincopy
  1. Fragment1 fragment1 = new Fragment1();
  2. addFragment(fragment1, "fragment1");

其中:

[java] view plaincopy
  1. private void addFragment(Fragment fragment, String tag) {
  2. FragmentManager manager = getSupportFragmentManager();
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.add(R.id.fragment_container,fragment, tag);
  5. transaction.addToBackStack(tag);
  6. transaction.commit();
  7. }

这个函数已经在前面几章用过N多次了,就不再讲了。
(3)、frag3 hide的代码:

[java] view plaincopy
  1. FragmentManager manager = getSupportFragmentManager();
  2. Fragment fragment = manager.findFragmentByTag("fragment3");
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.hide(fragment);
  5. transaction.addToBackStack("hide fragment3");
  6. transaction.commit();

也没什么难度,跟前面几篇不一样的地方就是调用了transaction.hide(fragment);函数;
(4)、frag3 show的代码:

[java] view plaincopy
  1. FragmentManager manager = getSupportFragmentManager();
  2. Fragment fragment = manager.findFragmentByTag("fragment3");
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.show(fragment);
  5. transaction.addToBackStack("show fragment3");
  6. transaction.commit();

这里也基本上与以前的操作代码都一样,只是使用了transaction.show(fragment);函数

2、在实战中的运用方法

如果我们使用replace来切换页面,那么在每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。
这是因为replace操作,每次都会把container中的现有的fragment实例清空,然后再把指定的fragment添加进去,就就造成了在切换到以前的fragment时,就会重新实例会fragment。
正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个。
这样就能做到多个Fragment切换不重新实例化:(基本算法如下)

[java] view plaincopy
  1. public void switchContent(Fragment from, Fragment to) {
  2. if (!to.isAdded()) {    // 先判断是否被add过
  3. transaction.hide(from).add(R.id.content_frame, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
  4. } else {
  5. transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
  6. }
  7. }

大家可能觉得这里有个问题,如果我们要show()的fragment不在最顶层怎么办?如果不在ADD队列的队首,那显然show()之后是不可见的;那岂不影响了APP逻辑。大家有这个想法是很棒的,但在APP中不存在这样的情况,因为我们的APP的fragment是一层层ADD进去的,而且我们的fragment实例都是唯一的,用TAG来标识,当退出的时候也是一层层剥离的,所以当用户的动作导致要添加某个fragment时,那说明这个fragment肯定是在栈顶的。

二、detach()、attach()

这两个函数的声明如下:

[java] view plaincopy
  1. public FragmentTransaction detach(Fragment fragment);
  2. public abstract FragmentTransaction attach(Fragment fragment);

detach(): 会将view与fragment分离,将此将view从viewtree中删除!而且将fragment从Activity的ADD队列中移除!所以在使用detach()后,使用fragment::isAdded()返回的值是false;但此fragment实例并不会删除,此fragment的状态依然保持着使用,所以在fragmentManager中仍然可以找到,即通过FragmentManager::findViewByTag()仍然是会有值的。
attach(): 显然这个方法与detach()所做的工作相反,它一方面利用fragment的onCreateView()来重建视图,一方面将此fragment添加到ADD队列中;这里最值得注意的地方在这里:由于是将fragment添加到ADD队列,所以只能添加到列队头部,所以attach()操作的结果是,最新操作的页面始终显示在最前面!这也就解释了下面的例子中,为了fragment2 detach()后,当再次attach()后,却跑到了fragment3的前面的原因。还有,由于这里会将fragment添加到Activity的ADD队列中,所以在这里调用fragment::isAdded()将返回True;
下面用一个例子来讲讲,有关这上面所讲解的知识,效果图如下:

  • (1)、同样,先依次添加Fragment1,Fragment2,Fragment3
  • (2)、然后点击“frag3 detach”,将fragment3的View视图删除,然后从ADD队列中将fragment移除。之后点击“fragment is added?”根据TOAST可以看出,fragment::isAdded()函数返回值是false;
  • (3)、然后点击“frag3 attach”,将fragment重新与Activity绑定,它有两个动作,一方面重建fragment视图,一方面将fragment添加到Activity的ADD队列中;所以这时候点击“fragment is added?”,fragment::isAdded()函数返回值是true;
  • (4)、然后点击“frag2 detach”,由于fragment2在fragment3之下,所以给fragment2使用detach,在界面上看不出任何效果。
  • (5)、但当点击“frag2 attach”时,问题出现了,由于attach()会做两件事,一方面重建fragment视图,一方面将fragment添加到Activity的ADD队列中;由于是ADD队列,所以肯定添加的位置肯定在队首;所以fragment2就显示在了最上方,把fragment3盖住了,这就是为什么在点击“frag2 attach”之后,却可以看到fragment2的视图的原因!

好了,下面就是代码部分了,这部分代码是在上一部分的上面添加了几个按钮而来的,直接看按钮点击时的代码操作:

1、点击frag3 detach按钮的代码

[java] view plaincopy
  1. FragmentManager manager = getSupportFragmentManager();
  2. Fragment fragment = manager.findFragmentByTag("fragment3");
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.detach(fragment);
  5. transaction.addToBackStack("detach fragment3");
  6. transaction.commit();

从代码也可以看到,没什么难度,这个函数的最难点在于知道detach()在执行过程中都干了什么!再重申一遍:一方面删除fragment的View视图;一方面将fragment从Activity的ADD队列中移除!说是Activity的ADD队列,倒不如说是container的ADD队列更贴切些;因为一个Activity上面可以有多个Container来盛装Fragment实例组,每一个Container都会被分配一个ADD队列来记录当前通过add()方法,添加到这个container里的所有fragment实例。
2、点击“frag3 attach”按钮的代码

[java] view plaincopy
  1. FragmentManager manager = getSupportFragmentManager();
  2. Fragment fragment = manager.findFragmentByTag("fragment3");
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.attach(fragment);
  5. transaction.addToBackStack("attach fragment3");
  6. transaction.commit();

依然,相比以前的fragment操作也只多了一个transaction.attach(fragment);没什么难度。关键仍然在于知道attach()操作都做了哪些事!再次重申:一方面重建fragment的View,注意是重建!另一方面,将fragment实例添加进container的ADD队列中;关于"frag2 detach"和"frag2 attach"的代码就不再贴出来了,跟frag3的一样。
好了,到这里,有关Fragment的操作都已经讲完了,下面就讲讲有关在Fragment操作中Android的BUG!

源码在文章底部给出

三、系统BUG——add()和replace()千万不要共用!!!

先写个例子来看一下问题:
这个例子分为两部分,

  • 第一部分:先利用add()函数,依次add进去fragment1,fragment2,fragment3,fragment4,fragment5,然后利用"print back stack"打印出当前在回退栈中每次操作的名称;每回退一次打一次回退栈内容,可见一切都是正常的,即回退栈顶的项,正是当前VIEW顶部显示的内容。
  • 第二部分,如果我们先利用add()函数,依次add进去fragment1,fragment2,fragment3,fragment4,然后再利用replace函数添加进去fragment5;然后利用"print back stack"打印出当前在回退栈中每次操作的名称;可以看到,当回退栈顶是"add fragment4"时,fragment4却没有出现,点击返回按钮,却把这个"add fragment4"的Transaction操作给返回了。同样的现象也发生在fragment2中;

还是先看看实现代码:
1、添加fragment,比如添加fragment1,其它fragment2,fragment3,fragment4同理

[java] view plaincopy
  1. Fragment1 fragment1 = new Fragment1();
  2. addFragment(fragment1, "add fragment1");

其中:

[java] view plaincopy
  1. private void addFragment(Fragment fragment, String tag) {
  2. FragmentManager manager = getSupportFragmentManager();
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.add(R.id.fragment_container, fragment);
  5. transaction.addToBackStack(tag);
  6. transaction.commit();
  7. }

2、replace fragment5:
代码上没什么难度,在添加到回退栈时,添加TAG:"replace fragment5"

[java] view plaincopy
  1. Fragment5 fragment5 = new Fragment5();
  2. FragmentManager manager = getSupportFragmentManager();
  3. FragmentTransaction transaction = manager.beginTransaction();
  4. transaction.replace(R.id.fragment_container, fragment5);
  5. transaction.addToBackStack("replace fragment5");
  6. transaction.commit();

3、打印出回退栈中的内容:
这里要讲一个函数了:

[java] view plaincopy
  1. public int getBackStackEntryCount();//获取回退栈中,Transaction回退操作的数量
  2. public BackStackEntry getBackStackEntryAt(int index);//根据索引得到回退栈变量

其中getBackStackEntryAt()返回的变量BackStackEntry,就是回退栈中保存每次transaction操作的变量;它有很多方法,其中BackStackEntry::getName()是获取Transacion操作的名字,即通过transaction.addToBackStack("replace fragment5");传进去的字符串。关于BackStackEntry的其它方法,靠大家自己去发掘啦,这个函数用的不多,就不再细讲了。

[java] view plaincopy
  1. TextView tv = (TextView) findViewById(R.id.tv_stack_val);
  2. FragmentManager manager = getSupportFragmentManager();
  3. int count = manager.getBackStackEntryCount();
  4. StringBuilder builder = new StringBuilder("回退栈内容为:\n");
  5. for (int i = --count;i>=0;i--){
  6. FragmentManager.BackStackEntry entry= manager.getBackStackEntryAt(i);
  7. builder.append(entry.getName()+"\n");
  8. }
  9. tv.setText(builder.toString());

好啦,代码看完了,要讲问题了。那问题来了,为什么在回退栈中有add fragment4和add fragment2的操作,却不显示呢?
问题出在了replace()操作上,replace()操作原意的实现应该是清空container中所有的fragment实例,然后再将指定的fragment添加到container的ADD队列中;但在清空时,他们的代码是这样写的:

[java] view plaincopy
  1. for (int i=0;i<mManager.mAdded.size(); i++) {
  2. Fragment old = mManager.mAdded.get(i);
  3. ……
  4. mManager.removeFragment(old, mTransition, mTransitionStyle);
  5. }

其中:mAdded就是我们前面说的container的ADD队列;看他的操作:
首先,先逐个得到mAdded队列中的fragment,即:

[java] view plaincopy
  1. Fragment old = mManager.mAdded.get(i);

然后,将这个fragment实例移除:

[java] view plaincopy
  1. mManager.removeFragment(old, mTransition, mTransitionStyle);

有没有看出什么问题?他把这个fragment从mAdded队列中直接移除了!!!!那这不打乱了原来的顺序了么,在移除下一个fragment时就根本对不上号了。看不懂?没关系,我们举个例子来讲:
比如,我们上面的,在mAdded队列中有1,2,3,4,5这五个fragment;
首先,当i=0时,移除1,这没错!但它是将mAdded队列中的1直接移除的哦!所以移除1以后,mAdded队列的值变成了2,3,4,5
这时候,当i=1时,删除的是3!!!!知道问题所在了吧!所以在删除3后,mAdded队列的值为2,4,5
所以当i=2时,删除的是5!!!!所以这就造成了为什么我们的fragment2和fragment4明明在回退栈中,即显示不出来的原因,因为他们在删除时根本就没有删除,而在回退栈回退时却又要跟着操作顺序来回退,即remove fragment5,逐个add进去fragment4,fragment3,fragment2,fragment1,而又由于fragment4和fragment2没有被删除,所以出现了错误,导致系统哪里出了问题,所以显示不出来,至于是哪里出了问题,我也不知道,因为我尝试了show()和attach() fragment4都还是没有效果。可能是系统底层的问题吧。所以,这里忠告大家,add()和replace()不能共用!!!!!

有关这个系统BUG的细节分析,请参考文章:

1、《Fragment(五)Transaction 源码分析》

2、《报给GOOGLE的BUG地址》

Fragment详解之四——管理Fragment(2)相关推荐

  1. Fragment详解之三——管理Fragment(1)

    前言:follow your heart,be your own king 相关文章: 1.<Fragment详解之一--概述> 2.<Fragment详解之二--基本使用方法> ...

  2. Android Fragment详解(二):Fragment创建及其生命周期

    Fragments的生命周期 每一个fragments 都有自己的一套生命周期回调方法和处理自己的用户输入事件. 对应生命周期可参考下图: 创建片元(Creating a Fragment) To c ...

  3. android编程fragment,详解Android中Fragment的两种创建方式

    onCreate():在创建fragment时系统会调用此方法.在实现代码中,你可以初始化想要在fragment中保持的那些必要组件,当fragment处于暂停或者停止状态之后可重新启用它们. onC ...

  4. android Fragment详解三:实现Fragment的界面

    为fragment添加用户界面 fragment一般作为activity的用户界面的一部分,把它自己的layout嵌入到activity的layout中.    一个 要为fragment提供layo ...

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

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

  6. Fragment详解之二——基本使用方法

    前言:依然没有前言--文章写的太快,生活过得太有章程,前言都不知道写什么了-- 相关文章: 1.<Fragment详解之一--概述> 2.<Fragment详解之二--基本使用方法& ...

  7. android fragment 优势,Android Fragment详解

    参考网址:Android Fragment详解 一.什么是Fragment? Fragment:是Android3.0开始新增的概念,意为碎片.Fragment是依赖于Activity的,不能独立存在 ...

  8. MTU和Fragment详解

    1. 基础知识 我们知道, 数据在网络上传输时, 要经过一段一段的链路.当数据从某一段链路的一端传到另一端的过程中, 需要考虑的是数据链路层协议, 在这一层, 我们观察到的数据包(PDU: Packe ...

  9. Git详解之四 服务器上的Git

    Git详解之四 服务器上的Git 服务器上的 Git 到目前为止,你应该已经学会了使用 Git来完成日常工作.然而,如果想与他人合作,还需要一个远程的 Git仓库.尽管技术上可以从个人的仓库里推送和拉 ...

最新文章

  1. android之AlertDialog 点击其它区域自己主动消失
  2. 解决浮层弹出如何加上datepicker,并且浮动在上面
  3. wxWidgets:wxNavigationEnabled< W >类模板的用法
  4. 数据结构课程设计---------最少换车次数问题
  5. js中json的创建和解析
  6. php监听网页日志,如何用php程序监听一个不断增长的日志文件
  7. android 申请usb权限,USB 权限申请流程
  8. 如何打造园本特色_如何打造一个可持续发展的特色观光园?
  9. 东华大学java期末_东华大学数据结构期末复习题!.doc
  10. Scrapy操作浏览器获取网易新闻数据
  11. (转载)Android content provider基础与使用
  12. Android 获取sim卡序列号
  13. EQMX+Nginx集群搭建
  14. 加密解密之 crypto-js 知识
  15. 10个好用的免费图片网站,绝对能在2021年设计上好帮手
  16. WPS参考文献编号及超链接
  17. Scrapy项目 - 数据简析 - 实现豆瓣 Top250 电影信息爬取的爬虫设计
  18. 启动fiddler导致浏览器无法上网的解决方法
  19. 个人计算机全都是多媒体计算机系统组成,多媒体计算机系统组成
  20. uni-app二维码、条形码扫码自定义

热门文章

  1. 【Git】Git 分支管理 ( 使用 git cherry-pick 命令提取提交记录应用于当前分支 | 创建新分支应用某个提交 | git cherry-pick 冲突处理 )
  2. 【Android 逆向】Android 中常用的 so 动态库 ( 拷贝 /system/lib/ 中的 Android 系统 so 动态库 )
  3. 【MATLAB】基本绘图 ( 图形设置 | 坐标轴开关 | box 开关 | 网格开关 | 坐标轴样式 )
  4. 【组合数学】递推方程 ( 递推方程示例 1 | 列出递推方程 )
  5. 【Android FFMPEG 开发】FFMPEG 方法中指针类型参数说明 ( 一维指针类型参数 | 二维指针类型参数 )
  6. vue-lazyload 的vue 懒加载的使用
  7. SSH port forwarding: bind: Cannot assign requested address
  8. [高级软件工程教学]结队第1次作业成绩公布
  9. ubuntu apt-get dpkg应用中的一些问题及解决方法
  10. 修改Project中的表名及字段名