Fragment应用之简述
Fragment的应用真的是越来越广泛了,之前Android在3.0版本加入Fragment的时候,主要是为了解决Android Pad屏幕比较大,空间不能充分利用的问题。但随着界面布局的复杂化,处理起来也更加的复杂,引入Fragment可以把activity拆分成各个部分。每个Fragment都有它自己的布局和生命周期。
一、Fragment的生命周期
Fragment生命周期.png-
onAttach()
作用:fragment已经关联到activity。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.i("onAttach_Fragment");
}
该方法有一个Activity类型的参数,代表绑定的Activity,获得activity的传递的值 就可以进行 与activity的通信里, 当然也可以使用getActivity(),前提是这个fragment已经和宿主的activity关联,并且没有脱离。
- **onCreate() **
作用:初始化Fragment,系统创建fragment的时候回调该方法,在该方法里面实例化一些变量,参数是:Bundle savedInstance, 用于保存 Fragment 参数, Fragement 也可以重写 onSaveInstanceState(BundleoutState) 方法, 保存Fragement状态。 -
onCreateView()
作用:初始化Fragment的布局。加载布局和findViewById的操作通常在此函数内完成,当系统用到fragment的时候 fragment就要返回它的view,越快越好 ,所以尽量在这里不要做耗时操作,比如从数据库加载大量数据,可进行各种判断省得每次都要加载,减少资源消耗,实例如下:
if(text==null){
Bundle args=getArguments();
text=args.getString("text");
}
if (view == null) {
view = inflater.inflate(R.layout.hello, null);
}
-
onActivityCreated()
作用:初始化那些你需要你的父Activity或者Fragment的UI已经被完整初始化才能初始化的元素。
执行该方法时,与Fragment绑定的Activity的onCreate方法已经执行完成并返回,在该方法内可以进行与Activity交互的UI操作,当执行onActivityCreated()的时候 activity的onCreate才刚完成。所以在onActivityCreated()调用之前 activity的onCreate可能还没有完成,所以不能再onCreateView()中进行 与activity有交互的UI操作,UI交互操作可以在onActivityCreated()里面进行。 -
onStart()
和activity一致,启动Fragement 启动时回调,,此时Fragement由不可见变为可见状态。 -
onResume()
执行该方法时,Fragment处于活动状态,用户可与之交互。激活Fragement 进入前台, 可获取焦点时激活。 -
onPause()
和activity一致 其他的activity获得焦点,这个Fragment仍然可见,但是用户不能与之交互。第一次调用的时候,指的是 用户 离开这个Fragment(并不是被销毁)。 -
onStop()
和activity一致, fragment不可见的, 可能情况:activity被stopped了或者 fragment被移除但被加入到回退栈中,一个stopped的fragment仍然是活着的如果长时间不用也会被移除。 -
onDestroyView()
Fragment中的布局被移除时调用。
表示Fragment销毁相关联的UI布局, 清除所有跟视图相关的资源,但未与Activity解除绑定,依然可以通过onCreateView方法重新创建视图。 -
onDestroy()
销毁Fragment。通常按Back键退出或者Fragment被回收时调用此方法。 -
onDetach()
Fragment解除与Activity的绑定。在onDestroy方法之后调用。
下面给出activity和fragment同时运行时候的生命周期:
- 开始启动:
03-10 16:55:57.722 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onCreate() 方法执行!
03-10 16:55:57.728 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onCreate() 方法执行!
03-10 16:55:57.728 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onCreateView() 方法执行!
03-10 16:55:57.728 1700-1700/com.liujc.fragmentlife D/TestFragment: 没有保存的数据!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onActivityCreated() 方法执行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onStart() 方法执行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onStart() 方法执行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onResume() 方法执行!
03-10 16:55:57.730 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onResume() 方法执行!
细心的你可能会发现为什么Fragment没走onAttach()方法呢?难道生命周期还有问题不成。其实Fragment的onAttach()方法有2个重载onAttach(Context context)和onAttach(Activity activity),我的测试机用的android 5.0系统,而在API低于 23 的版本中不会去调用onAttach(Context context),只会去调用onAttach(Activity)。然后把两个方法都加上运行一下结果如下:
03-10 17:19:08.539 19010-19010/com.liujc.fragmentlife D/MainActivity: Activity onCreate() 方法执行!
03-10 17:19:08.546 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onAttach(Activity activity) 方法执行!
03-10 17:19:08.546 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onCreate() 方法执行!
03-10 17:19:08.547 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onCreateView() 方法执行!
03-10 17:19:08.547 19010-19010/com.liujc.fragmentlife D/TestFragment: 没有保存的数据!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onActivityCreated() 方法执行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/MainActivity: Activity onStart() 方法执行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onStart() 方法执行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/MainActivity: Activity onResume() 方法执行!
03-10 17:19:08.548 19010-19010/com.liujc.fragmentlife D/TestFragment: Fragment onResume() 方法执行!
- 按下home按键
03-10 17:00:08.455 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onPause() 方法执行!
03-10 17:00:08.456 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onPause() 方法执行!
03-10 17:00:09.048 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onSaveInstanceState() 方法执行!
03-10 17:00:09.052 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onStop() 方法执行!
03-10 17:00:09.054 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onStop() 方法执行!
- 再回到界面
03-10 17:01:20.870 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onRestart() 方法执行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onStart() 方法执行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onStart() 方法执行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/MainActivity: Activity onResume() 方法执行!
03-10 17:01:20.873 1700-1700/com.liujc.fragmentlife D/TestFragment: Fragment onResume() 方法执行!
- 销毁activity
03-10 17:05:53.900 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onPause() 方法执行!
03-10 17:05:53.901 22559-22559/com.liujc.fragmentlife D/MainActivity: Activity onPause() 方法执行!
03-10 17:05:54.435 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onStop() 方法执行!
03-10 17:05:54.435 22559-22559/com.liujc.fragmentlife D/MainActivity: Activity onStop() 方法执行!
03-10 17:05:54.437 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onDestroyView() 方法执行!
03-10 17:05:54.441 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onDestroy() 方法执行!
03-10 17:05:54.441 22559-22559/com.liujc.fragmentlife D/TestFragment: Fragment onDetach() 方法执行!
03-10 17:05:54.441 22559-22559/com.liujc.fragmentlife D/MainActivity: Activity onDestroy() 方法执行!
可以看出 当现实fragment的时候都先执行activity方法,当销毁的时候都是现执行 fragment的方法,这样更好理解fragment是嵌套在activity中。
二、将Fragment添加到Activity之中
可以通过在Activity布局文件中声明Fragment,用Fragment标签把Fragment插入到Activity的布局中,或者是用应用程序源码将它添加到一个存在的ViewGroup中。但Fragment并不是一个定要作为Activity布局的一部分,Fragment也可以为Activity隐身工作。
-
在activity的布局文件里声明fragment。
可以像为view一样为fragment指定布局属性。例如:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.liujc.test.FragmentOne"
android:id="@+id/fragment_one"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
fragment标签中的android:name 属性指定了布局中实例化的Fragment类。
当系统创建activity布局时,它实例化了布局文件中指定的每一个fragment,并为它们调用onCreateView()函数,以获取每一个fragment的布局。系统直接在元素的位置插入fragment返回的View。
注意:每个fragment都需要一个唯一的标识,如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如移除)。
为fragment提供ID有三种方法:
- 用android:id属性提供一个唯一的标识。
- 用android:tag属性提供一个唯一的字符串。
- 如果上述两个属性都没有,系统会使用其容器视图(view)的ID。
-
通过编码将fragment添加到已存在的ViewGroup中。
在activity运行的任何时候,你都可以将fragment添加到activity布局中。要管理activity中的fragment,可以使用FragmentManager。可以通过在activity中调用getFragmentManager()获得。使用FragmentManager 可以做如下事情,包括:
- 使用findFragmentById()(用于在activity布局中提供有界面的fragment)或者findFragmentByTag()获取activity中存在的fragment(用于有界面或者没有界面的fragment)。
- 使用popBackStack()(模仿用户的BACK命令)从后台栈弹出fragment。
- 使用addOnBackStackChangedListener()注册一个监听后台栈变化的监听器。
在Android中,对Fragment的事务操作都是通过FragmentTransaction来执行。
FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
操作大致可以分为两类:
-
显示:
add()
、replace()
、show()
、attach()
。
**transaction.add() **
往Activity中添加一个Fragment。
**transaction.replace() **
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体。
**transaction.show() **
显示之前隐藏的Fragment。
**attach() **
重建view视图,附加到UI上并显示。 -
隐藏:
remove()
、hide()
、detach()
。
** transaction.remove() **
从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。
transaction.hide()
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁。
detach()
会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
注意:
- 调用show() & hide()方法时,Fragment的生命周期方法并不会被执行,仅仅是Fragment的View被显示或者隐藏。
- 执行replace()时(至少两个Fragment),会执行第二个Fragment的onAttach()方法、执行第一个Fragment的onPause()-onDetach()方法,同时containerView会detach第一个Fragment的View。
- 执行add()方法执行onAttach()-onResume()的生命周期,相对的remove()就是执行完成剩下的onPause()-onDetach()周期。
add方式实现fragment的效果就是:切换fragment时不会重新创建,是什么样子切换回来还是什么样子;
用replace的效果就是:切换fragment时每次都会重新创建初始化。
从Activity中取得FragmentTransaction的实例:
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
用add()函数添加fragment,并指定要添加的fragment以及要将其插入到哪个视图(view)之中(注意commit事务):
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
- 添加没有界面的fragment。
也可以使用fragment为activity提供后台动作,却不呈现多余的用户界面。
想要添加没有界面的fragment ,可以使用add(Fragment, String)(为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID)。这样添加了fragment,但是,因为还没有关联到activity布局中的视图(view) ,收不到onCreateView()的调用。所以不需要实现这个方法。对于无界面fragment,字符串标签是唯一识别它的方法。如果之后想从activity中取到fragment,需要使用findFragmentByTag()。
三、Fragment与Activity交互
- Activity中已经有了该Fragment的引用,直接通过该引用进行交互。
如果没引用可以通过调用fragment的函数findFragmentById()或者findFragmentByTag(),从FragmentManager中获取Fragment的索引,例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
- 在Fragment中可以通过getActivity得到当前绑定的Activity的实例。
- 创建activity事件回调函数,在fragment内部定义一个回调接口,宿主activity来实现它。
四、Fragment事务后台栈
在调用commit()之前,可以将事务添加到fragment事务后台栈中(通过调用addToBackStatck())。这个后台栈由activity管理,并且允许用户通过按BACK键回退到前一个fragment状态。
下面的代码中一个fragment代替另一个fragment,并且将之前的fragment状态保留在后台栈中:
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
注意:
- 如果添加多个变更事务(例如另一个add()或者remove())并调用addToBackStack(),那么在调用commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且BACK键可以将它们一起回退。
- 当移除一个fragment时,如果调用了addToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。
- 调用commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用executePendingTransations()立即执行由commit()提交的事务。
- 只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss()。
五、Fragment的setUserVisibleHint()
Android应用开发过程中,ViewPager同时加载多个fragment,以实现多tab页面快速切换, 但是fragment初始化时若加载的内容较多,就可能导致整个应用启动速度缓慢,影响用户体验。 为了提高用户体验,我们会使用一些懒加载方案,实现加载延迟。这时我们会用到getUserVisibleHint()与setUserVisibleHint()这两个方法。
/**
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = !isVisibleToUser;
}
/**
* @return The current value of the user-visible hint on this fragment.
* @see #setUserVisibleHint(boolean)
*/
public boolean getUserVisibleHint() {
return mUserVisibleHint;
}
从上述源码注释我们可以看出,当fragment被用户可见时,setUserVisibleHint()会调用且传入true值,当fragment不被用户可见时,setUserVisibleHint()则得到false值。而在传统的fragment生命周期里也看不到这个函数。可以看出其实这个setUserVisibleHint()方法算是手动调用的,并不是在Fragment的生命周期中自动调用。