Fragment你应该明白的一些疑难点
# Fragment概述
Fragment是Activity中用户界面的一个行为或者说是一部分。主要是支持大屏幕上动态显示和更为灵活的去组合或是交换UI组件,通过将Activity的布局分割成若干个fragment,可以在运行时编辑activity的呈现,并且那些变化会被保存在由activity管理的后台栈里面。 **Fragment必须总是被嵌入到一个activity之中,**并且fragment的生命周期直接接受其宿主activity的生命周期的影响。你可以认为fragment是activity的一个模块零件,它有自己的生命周期,接收它自己的输入的事件,并且可以在activity运行时添加或者删除。 **应该将每一个fragment设计为模块化和可复用化的activity组件。**也就是说,你可以在多个activity中引用同一个fragment,因为fragment定义了它自己的布局,并且使用它本身生命周期回调的行为。
先看fragment生命周期图:
Fragment周期图
在看Fragment依附于Activity的生命状态图:
Fragment和Activity生命周期综合图
Fragment生命周期中的那么多方法,快来学习一下吧!go go go
# Fragment生命周期方法含义:
public void onAttach(Context context)
onAttach()方法会在Fragment与Activity窗口关联后立刻调用。从该方法开始,就可以通过Fragment.getActivity()方法获取与Fragment关联的Activtiy窗口对象,但因为Fragment的控件未初始化,所以不能够操作控件。public void onCreate(Bundle savedInstanceState)
在调用完onAttach()执行完之后,立即就会调用onCreate()方法,可以在Bundle对象中获取一些在Activity中传过来的数据。通常会在该方法中读取保存的转态,获取或初始化一些数据。在该方法中不要进行耗时操作,不然Activity窗口不会显示。public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
该方法是Fragment很重要的一个生命周期方法,因为会在该方法中创建Fragment显示的View,其中inflater是用来装载布局文件的,container是标签的父标签对应对象,saveInstanceState参数可以获取Fragment保存的转态,如果未保存那么就为null。 public void onViewCreated(View view,Bundle savedInstanceState)
Android在创建完Fragment中的View对象之后,会立刻回调该方法。其中view参数就是onCreateView中返回的view,而bundle对象用于一般用途。public void onActivityCreated(Bundle savedInstanceState)
在Activity的onCreate()方法执行完之后,Android系统会立刻调用该方法,表示Activity窗口已经初始化完成,从这一个时候开始,就可以在Fragment中使用getActivity().findViewById(R.id.xxx);来操作Activity中的view了。-
public void onStart()
这个没啥可讲的,但是一个细节需要知道,当系统调用该方法的时候,fragment已经显示在UI上了,但还不能进行互动,因为onResume()方法还没有执行完。 public void onResume()
该方法为fragment从创建到显示Android系统调用的最后一个生命周期方法,调用完该方法时候,fragment就可以与用户互动了。public void onPause()
fragment由活动状态变成非活跃执行的第一个回调方法,通常可以在这个方法中保存一些需要临时暂停的工作。例如:存音乐播放速度,然后在onResume()方法中恢复音乐播放进度。public void onStop()
当onStop()返回的时候,fragment将从屏幕上消失。public void onDestoryView()
该方法的调用意味着在onCreateView()中创建的视图都将被移除。public void onDestroy()
Android在Fragment不再使用时会调用该方法,要注意的是~这是Fragment还和Activity是藕断丝连!并且可以获得Fragment对象,但无法对获得的Fragment进行任何操作。public void onDetach()
为Fragment生命周期中的最后一个方法,当该方法执行完后,Fragment与Activity不再有关联。
# Fragment比Activity多了几个额外的生命周期回调方法:
- **onAttach(Activity)😗*当Fragment和Activity发生关联时使用。
- **onCreateView(LayoutInflater,ViewGroup,Bundle)😗*创建该Fragment的视图
- **onActivityCreate(Bundle)😗*当Activity的onCreate()方法返回时调用
- **onDestoryView()😗*与onCreateView相对应,当该Fragment的视图被移除时调用
- **onDetach()😗*与onAttach()相对应,当Fragment与Activity关联被取消时调用 注意:除了onCreateView()方法,其他的所有的方法如果你重写了,必须调用父类对于该方法的实现
# 管理fragment生命周期与管理activity生命周期很相像
像activity一样,fragment也有三种状态:
Resumed fragment在运行中的activity中可见
Paused 另一个activity处于前台且得到焦点,但是这个fragment所在的activtiy仍然可见(前台activity部分透明,或者没有覆盖全屏)。
Stopped
fragment不可见。要么宿主activity已经停止,要么fragment已经从activity上移除,但已被添加到后台栈中。一个停止的fragment仍然活着(所有的状态和成员信息仍然由系统保留着)。但是,它对于用户来讲已经不再可见,并且如果activity被杀掉,它也将被杀掉。
————————————————————————————————
如果activity的进程被杀掉了,在activity被重新创建时,你恢复fragment状态。可以执行fragment的onSaveIntanceState()来保存状态(注意:fragment是在onCreate(),onCreateView()或者onActivityCreate()中进行恢复)
在生命周期方面,activity和fragment之间一个很重要的不同
就是在各自的后台栈中是如何存储的。当
activity停止时
,默认情况下activity被安置在由系统管理的activity后台栈中;
fragment
仅当在一个事务被移除时,通过显式调用addToBackStack()请求保存的实例,该fragment才被置于由宿主activity管理的后台栈。
要创建一个fragment,必须创建一个fragment的子类,一般情况下,我们至少需要实现以下几个fragment生命周期的方法:onCreate(),onCreateView(),onPause()
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); }复制代码
1
2
3
4
5
6
7inflate()函数需要以下三个参数:
(1).要inflate的布局的资源Id
(2).被inflate的布局的父ViewGroup
(3).一个布尔值,表明在inflate期间被inflate的布局是否应该附上ViewGroup(第二个参数container)。(在这个例子中传入的是false,因为系统已经将被inflate的布局插入到容器中(container)——传入true会在最终的布局里创建一个多余的ViewGroup)
# Fragment与Activity之间的交互:
将Fragment添加到activity之中 可以通过在activtiy布局文件中声明fragment,用fragment标签把fragment插入到activity的布局中,或者是用应用程序源码将它添加到一个存在的ViewGroup中。但fragment并不是一定要作为activity布局的一部分,fragment也可以为activity隐身工作。 (1).在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.example.test.FragmentOne" android:id="@+id/fo" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>复制代码
1
2
3
4
5
6
7
8
9
10
11fragment标签中的android:name属性指定了布局中实例化Fragment类。 当系统创建activity布局时,它实例化了布局文件中指定的每一个fragment,并为它们调用onCeateView()函数,以获取每一个fragment的布局。系统直接在元素的位置插入fragment返回的View。 注意:每个fragment都需要一个唯一的标识,如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如:移除fragment),为fragment提供了ID有三种方法: 1:>用android:id属性提供一个唯一的标识 2:>用android:tag属性提供一个唯一的字符串。 3:>如果上述两种属性都没有,系统会使用其容器视图(view)的ID。 (2).通过编码将fragment添加到已存在的ViewGroup中 在activity运行的任何时候,你都可以将fragment添加到activity布局中。要管理activity中的fragment,可以使用FragmentManager。可以通过在activity中调用getFragmentManager()获得。使用FragmentManager可以做如下事情,包括: 1:>使用findFragmentById()(用于在activity布局中提供有界面的fragment或者findFragmentByTag()获取activity中存在的fragment(用于有界面或者没有界面的fragment)) 2:>使用popBackStack()(模仿用户的Back命令)从后台栈弹出fragment。 3:>使用addOnBackStackChangeListener()注册一个监听后台栈变化的监听器。 在android中,对fragment的事务操作都是通过FragmentTranSaction来执行。操作大致可以分为两类: 1:>显示:add(), replace(), show(), attach() 2:>隐藏:remove(), hide(), detach() 说明:调用show() & hide()方法时,Fragment的生命周期方法并不会被执行,仅仅是Fragment的View被显示或者隐藏。执行replace()时(至少两个Fragment),会执行第二个Fragment的onAttach()方法、执行第一个Fragment的onPause()-onDetach()方法,同时containerView会detach第一个Fragment的View。add()方法执行onAttach()-onResume()的生命周期,相对的remove()就是执行完成剩下的onPause()-onDetach()周期。 可以像下面这样从Activity中取得FragmentTransaction的实例:
FragmentManager fragmentManager = getFragmentManager() FragmentTransaction fragmentTransaction = fragmentManager .beginTransaction();复制代码
1
2
3可以用add()函数添加fragment,并指定要添加的fragment以及要将其插入到哪个视图(view)之中(注意commit事务):
ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit();复制代码
1
2
3(3).添加没有界面的fragment 也可以使用fragment为activity提供后台动作,却不呈现多余的用户界面。想要添加没有界面的fragment ,可以使用add(Fragment, String)(为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID)。这样添加了fragment,但是,因为还没有关联到activity布局中的视图(view) ,收不到onCreateView()的调用。所以不需要实现这个方法。对于无界面fragment,字符串标签是唯一识别它的方法。如果之后想从activity中取到fragment,需要使用findFragmentByTag()。
Fragment与Activity之间的交互可以通过Fragment.setArguments(Bundle args)以及Fragment.getArguments()来实现。
# Fragment状态的持久化:
由于Activity会经常性的发生配置变化,所以依附它的Fragment就有需要将其状态保存起来问题。下面有两种常用的方法去将Fragment的状态持久化。
方法一 可以通过
protected void onSaveInstanceState(Bundle outState),protected void onRestoreInstanceState(Bundle savedInstanceState)
状态保存和恢复的方法将状态持久化。方法二(更为方便,让Android自动帮我们保存Fragment状态) **<1>.**我们只需要将Fragment在Activity中作为一个变量整个保存,只要保存了Fragment,那么Fragment的状态就得到保存了,所以我们就可以通过下面方法,进行获取Fragment数据。
FragmentManager.putFragment(Bundle bundle, String key, Fragment fragment) 是在Activity中保存Fragment的方法。 FragmentManager.getFragment(Bundle bundle, String key) 是在Activity中获取所保存的Frament的方法。复制代码
1
2
3
4**<2>.**很显然,上述<1>中的key就传入Fragment的id,fragment就是你要保存状态的fragment,但,我们注意到上面的两个方法,第一个参数都是Bundle,这就意味着FragmentManager是通过Bundle去保存Fragment的。但是,这个方法仅仅能够保存Fragment中的控件状态,比如说:EditText中用户已经输入的文字(注意:在这里,控件需要设置一个id值,否则Android将不会为我们保存该控件的状态),而Fragment中需要持久化的变量依然会丢失,但依然有解决方法,就是利用方法一! **<3>.**下面给出状态的持久化实例代码:
/** Activity中的代码 **/ FragmentB fragmentB; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_activity); if( savedInstanceState != null ){ fragmentB = (FragmentB) getSupportFragmentManager() .getFragment(savedInstanceState,"fragmentB"); } init(); } @Override protected void onSaveInstanceState(Bundle outState) { if( fragmentB != null ){ getSupportFragmentManager() .putFragment(outState,"fragmentB",fragmentB); } super.onSaveInstanceState(outState); } /** Fragment中保存变量的代码 **/ @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { AppLog.e("onCreateView"); if ( null != savedInstanceState ){ String savedString = savedInstanceState .getString("string"); //得到保存下来的string } View root = inflater.inflate(R.layout.fragment_a,null); return root; } @Override public void onSaveInstanceState(Bundle outState) { outState.putString("string","anAngryAnt"); super.onSaveInstanceState(outState); }复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 静态的使用Fragment
1.集成Fragment,重写onCreateView()决定Fragment的布局 2.在Activity中声明此Fragment,就和普通的View一样。
# Fragment常用的API
- android.support.v4.app.Fragment:主要用于定义Fragment
- android.support.v4.app.FragmentManager :主要用于在Activity中操作Fragment,可以使用
FragmentMagager.findFragmentById()和FragmentMagager.findFragmentByTag()
等方法去找到一个Fragment - android.support.v4.app.FragmentTransaction:保证一些Fragment操作的原子性,熟悉事务这个词。
- 主要的操作都是FragmentTransaction的方法(一般我们为了向下兼容,都会使用support.v4包里面的Fragment)
getFragmentManager() //Fragment若使用的是support.v4包中的,那就使用getSupportFragmentManager代替
主要的操作都是FragmentTransaction的方法:
FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务 //往Activity中添加一个Fragment transaction.add() transaction.remove() //从Activity中移除一个Fragment,如果被移除的Fragment没有添 加到回退栈 (回退栈后面会详细说),这个Fragment实例将会被销毁。 transaction.replace() //使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~ transaction.hide() //隐藏当前的Fragment,仅仅是设为不可见,并不会销毁 transaction.show() //显示之前隐藏的Fragment detach() //当fragment被加入到回退栈的时候,该方法与*remove()*的作用是相同的, //反之,该方法只是将fragment从视图中移除, //之后仍然可以通过*attach()*方法重新使用fragment, //而调用了*remove()*方法之后, //不仅将Fragment从视图中移除,fragment还将不再可用。 attach() //重建view视图,附加到UI上并显示。 transatcion.commit() //提交一个事务复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29在调用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();复制代码
1
2
3
4
5注意: 如果添加多个变更事务(例如另一个add()或者remove())并调用addToBackStack(),那么在调用commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且BACK键可以将它们一起回退。 当移除一个fragment时,如果调用了addToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。 调用commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用executePendingTransations()立即执行由commit()提交的事务。 只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss()
# 管理Fragment回退栈
跟踪回退栈的状态
我们通过实现
OnBackStackChangedListener
1接口来实现回退栈状态跟踪,具体代码如下:
//implements接口 public class XXX implements FragmentManager.OnBackStackChangedListener //实现接口所要实现的方法 @Override public void onBackStackChanged() { //do whatevery you want } //设置回退栈监听接口 getSupportFragmentManager().addOnBackStackChangedListener(this);复制代码
1
2
3
4
5
6
7
8
9管理回退栈 (1).
FragmentTransaction.addToBackStack(String)
将一个刚刚添加的Fragment加入到回退栈中 (2).getSupportFragmentManager().getBackStackEntryCount()
获取回退栈中的实体数量 (3).getSupportFragmentManager().popBackStack(String name, int flags)
根据name立刻弹出栈顶的fragment (4).getSupportFragmentManager().popBackStack(int id, int flags)
根据id立刻弹出栈顶的fragment