Android - fragment Manager
fragment基本使用:
http://www.cnblogs.com/qlky/p/5415679.html
Fragmeng优点
- Fragment可以使你能够将activity分离成多个可重用的组件,每个都有它自己的生命周期和UI。
- Fragment可以轻松得创建动态灵活的UI设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
- Fragment是一个独立的模块,紧紧地与activity绑定在一起。可以运行中动态地移除、加入、交换等。
- Fragment提供一个新的方式让你在不同的安卓设备上统一你的UI。
- Fragment 解决Activity间的切换不流畅,轻量切换。
- Fragment 替代TabActivity做导航,性能更好。
- Fragment 在4.2.版本中新增嵌套fragmeng使用方法,能够生成更好的界面效果。
- Fragment做局部内容更新更方便,原来为了到达这一点要把多个布局放到一个activity里面,现在可以用多Fragment来代替,只有在需要的时候才加载Fragment,提高性能
生命周期
1、FragmentManager
要管理activity中的fragments,你就需要使用FragmentManager。通过getFragmentManager()或getSupportFragmentManager()获得
常用的方法有:
- manager.findFragmentById(); //根据ID来找到对应的Fragment实例,主要用在静态添加fragment的布局中,因为静态添加的fragment才会有ID
- manager.findFragmentByTag();//根据TAG找到对应的Fragment实例,主要用于在动态添加的fragment中,根据TAG来找到fragment实例
- manager.getFragments();//获取所有被ADD进Activity中的Fragment
2、FragmentTransaction
一般用来对当前的Fragment进行管理,包括add,replace,remove;
常用的针对Fragment的方法有:
- //将一个fragment实例添加到Activity的最上层
- add(int containerViewId, Fragment fragment, String tag);
- //将一个fragment实例从Activity的fragment队列中删除
- remove(Fragment fragment);
- //替换containerViewId中的fragment实例,注意,它首先把containerViewId中所有fragment删除,然后再add进去当前的fragment
- replace(int containerViewId, Fragment fragment);
三、有关回滚——FragmentTransaction
1、FragmentTransaction事务回滚使用方法:
上部分,我们讲了有关添加、删除Fragment的操作,想将上一次commit的操作返回时,要怎么做呢。这就需要FragmentTransaction的回滚功能了。
要使用回滚功能,只需要要使用下面两个代码:
在transaction.commit()之前,使用addToBackStack()将其添加到回退栈中。
transaction.addToBackStack(String tag);
在需要回退时,使用popBackStack()将最上层的操作弹出回退栈。
manager.popBackStack();
这里的popBackStack()是弹出默认的最上层的栈顶内容。
当栈中有多层时,我们可以根据id或TAG标识来指定弹出到的操作所在层。函数如下:
- void popBackStack(int id, int flags);
- void popBackStack(String name, int flags);
其中
- 参数int id是当提交变更时transaction.commit()的返回值。
- 参数string name是transaction.addToBackStack(String tag)中的tag值;
- 至于int flags有两个取值:0或FragmentManager.POP_BACK_STACK_INCLUSIVE;
- 当取值0时,表示除了参数一指定这一层之上的所有层都退出栈,指定的这一层为栈顶层;
- 当取值POP_BACK_STACK_INCLUSIVE时,表示连着参数一指定的这一层一起退出栈;
- 除了这几个函数,还有下面几个函数:有关他们的使用,我们在这小部分结尾时会提到
- popBackStackImmediate()
- popBackStackImmediate(String tag)
- popBackStackImmediate(String tag, int flag)
- popBackStackImmediate(int id, int flag)
2、回退栈(back stack)状态改变监听
FragmentManager还为我们提供了监控回退栈状态改变的方法:
- FragmentManager::addOnBackStackChangedListener(listener);//添加监听器
- FragmentManager::removeOnBackStackChangedListener(listener);//移除监听器
通过添加监听器,就可以在回退栈内容改变时,及时收到通知;
3、Transaction事务回退的原则
这里我们着重讲一下,回退是以commit()提交的一次事务为单位的,而不是以其中的add,replace等等操作为单位回退的,即,如果我们在一次提交是添加了fragment2,fragment3,fragment4,那么回退时,会依据添加时的顺序,将它们一个个删除,返回到没有添加fragment4,fragment3,fragment2的状态。
一、hide()、show()
1、基本使用
这两个函数的功能非常简单,
- public FragmentTransaction hide(Fragment fragment);//将指定的fragment隐藏不显示
- public FragmentTransaction show(Fragment fragment);//将以前hide()过的fragment显示出来
2、在实战中的运用方法
如果我们使用replace来切换页面,那么在每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。
这是因为replace操作,每次都会把Container中的现有的fragment实例清空,然后再把指定的fragment添加进去,就就造成了在切换到以前的fragment时,就会重新实例会fragment。
正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个。
这样就能做到多个Fragment切换不重新实例化:(基本算法如下)
public void switchContent(Fragment from, Fragment to) { if (!to.isAdded()) { // 先判断是否被add过 transaction.hide(from).add(R.id.content_frame, to).commit(); // 隐藏当前的fragment,add下一个到Activity中 } else { transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个 } }
大家可能觉得这里有个问题,如果我们要show()的fragment不在最顶层怎么办?如果不在ADD队列的队首,那显然show()之后是不可见的;那岂不影响了APP逻辑。大家有这个想法是很棒的,但在APP中不存在这样的情况,因为我们的APP的fragment是一层层ADD进去的,而且我们的fragment实例都是唯一的,用TAG来标识,当退出的时候也是一层层剥离的,所以当用户的动作导致要添加某个fragment时,那说明这个fragment肯定是在栈顶的。
二、detach()、attach()
这两个函数的声明如下:
- public FragmentTransaction detach(Fragment fragment);
- 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;
三、Fragment参数传递
在关Fragment间参数的传递,有两种情况:
- 第一种情况:同一个container中不同fragment间的参数传递。这种情况一般发生在fragment跳转时,上一个Fragment将参数传递给下一个Fragment。
- 第二种情况:是同一个Activity中,不个container间Fragment的参数传递。
有关第一种情况,以前写过一篇文章,详细说明了上一个Fragment将参数传递给下一个Fragment,及数据回传的方法。详细参见:《Fragment跳转时传递参数及结果回传的方法》
当我们实例化自定义Fragment时,官方推荐Fragment.setArguments(Bundle bundle)这种方式来传递参数,而不推荐通过构造方法直接来传递参数
在屏幕旋转时,activity会重构,而依附在activity上的fragment最终会通过反射无参构造实例化一个新的Fragment,并且给mArgments初始化为原先的值,而原来的Fragment实例的数据都丢失了,并重新进行了初始化
通过上面的分析,我们可以知道Activity重新创建时,会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失,但是通过Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来。所以尽量使用Fragment.setArguments(Bundle bundle)方式来传递参数
二、同一个Activity,不同container间的参数传递
这里到了这篇文章的重点内容了哦,这可并不是说上一部分不重要哈,其实上一部分要比这部分重要!同一个Container中不同Fragment间的参数传递一般的工程都会用到的,所以大家一定要看
这里有多种实现方法,最可取的是方法三。我们由简到易慢慢讲。
我们想使两个fragment实例要能通信,那如果我们都能通过findViewById()找到所有的控件,直接操控的话,岂不就实现了。而通过findViewById()能找到所有控件实例的地方就是在Activity中了,所以这就有了方法一。
方法一:直接在Activity中操作
可见,直接在activity中操作各个fragment的控件就可以实现消息互传。但,这样真的好吗?如果每个fragment中的控件都在Activity中操作,那还要fragment干嘛!最最起码,应该每个fragment负责自己的控件操作才对嘛!
所以,我们对这种方法进行改进,将点击Item的赋值操作放到fragment1中去。所以,这就有方法二;
方法二:直接在fragment中操作
在这里我们会把所有方法写在Fragment1中,这里涉及到两方面的内容:
第一:在Fragment中如何获得自己控件的引用,比较这里Fragment1里的listview控件。
第二:在Fragment中如何获得其它Fragment页面中控件的引用,比如这里Fragment2里的TextView控件。
首先,获取自己控件引用的方法:
方法一:在onCreateView()中获取。
由于在onCreateView()中,还没有创建视图,所以在这里如果使用getView()方法将返回空。所以如果要获取其实图中指定控件的引用,只用用inflater.inflate()返回的rootView;在这个rootView()中用findViewById来查找。
方法二:在onActivityCreated()函数中获取。
从《Fragment详解之一——概述》的流程图中可以看到,onActivityCreated()回调会在Activity的OnCreate()执行完成后再执行,也就是说,onActivityCreated()会在Activity的OnCreate()工作完成以后才会执行。所以当执行到onActivityCreated()的时候Activity已经创建完成,它其中的各个fragment也视图等等的也都已经创建完成。所在可以在这里获取跟Activity相关的各种资源。第二个问题中的获取其它Fragment页面中控件的引用也是在onActivityCreated()中来做的。先看看在onActivityCreated()中如何获得自己视图中控件的引用吧
然后,获得其它Fragment页面中控件的引用的方法
在上面已经说了,要获取Activity中的资源,就必须等Acitivity创建完成以后,所以必须放在onActivityCreated()回调函数中。
其获取方法为:
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//获取其它fragment中的控件引用的唯一方法!!! }
我们这里直接在fragment1中操作了fragment2的控件,这样就违背了模块分离的思想,我们应该让他们各自处理各自的代码才好。所以,考虑到将他们分离,我们这里就出现了方法三。
方法三:在各自的fragment中操作
好,我们先想想要怎么解决这个问题,首先,我们把各自的事件放在各自的fragment中处理,即在fragment1中能得到当前用户点击的ITEM的String字符串,在fragment2中可以设置textview的值。那问题来了,各自获得了各自的东东,那通过什么让他们交互呢?
答案显然是activity。
很显然,在activity中可以直接通过FragmentManager::findFragmentById()来获取fragment2的实例。进而调用fragment2中的任意方法,这就实现了与fragment2的通信。
那fragment1又怎么把结果回传给Activity呢?大家如果在看到这里之前先看了《Fragment跳转时传递参数及结果回传的方法》就很容易想到,用回调!请记住。在不同页面中回传结果,回调是万能的解决方案。