Fragment的理解, Fragment的生命周期

Fragment是什么

说了半天的Fragment,也看到这么多次Fragment这个名词出现,那么Fragment到底是什么东东呢?定义又是如何?

Fragment也可以叫为“片段”,但我觉得“片段”中文叫法有点生硬,还是保持叫Fragment比较好,它可以表示Activity中的行为或用户界面部分。我们可以在一个Activity中用多个Fragment组合来构建多窗格的UI,以及在多个Activity中重复使用某个Fragment。它有自己的生命周期,能接受自己的输入,并且可以在 Activity 运行时添加或删除Fragment(有点像在不同 Activity 中重复使用的“子 Activity”)。

简单来说,Fragment其实可以理解为一个具有自己生命周期的控件,只不过这个控件又有点特殊,它有自己的处理输入事件的能力,有自己的生命周期,又必须依赖于Activity,能互相通信和托管。

Fragment生命周期

如图:

这张图是Fragment生命周期和Activity生命周期对比图,可以看到两者还是有很多相似的地方,比如都有onCreate(),onStart(),onPause(),onDestroy()等等,因为Fragment是被托管到Activity中的,所以多了两个onAttach()和onDetach()。这里讲讲与Activity生命周期不一样的方法。

onAttach()

Fragment和Activity建立关联的时候调用,被附加到Activity中去。

onCreate()

系统会在创建Fragment时调用此方法。可以初始化一段资源文件等等。

onCreateView()

系统会在Fragment首次绘制其用户界面时调用此方法。 要想为Fragment绘制 UI,从该方法中返回的 View 必须是Fragment布局的根视图。如果Fragment未提供 UI,您可以返回 null。

onViewCreated()

在Fragment被绘制后,调用此方法,可以初始化控件资源。

onActivityCreated()

当onCreate(),onCreateView(),onViewCreated()方法执行完后调用,也就是Activity被渲染绘制出来后。

onPause()

系统将此方法作为用户离开Fragment的第一个信号(但并不总是意味着此Fragment会被销毁)进行调用。 通常可以在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。

onDestroyView()

Fragment中的布局被移除时调用。

onDetach()

Fragment和Activity解除关联的时候调用。

但需要注一点是:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现。

还有一般在启动Fragment的时候,它的生命周期就会执行这几个方法。

Fragment怎么用

前面介绍了半天,不耐烦的人会说,这么多废话,也不见的到底是如何使用,毕竟我们是开发者,需要的使用方式,那么现在就来说说用法如何吧。两种方式:静态用法和动态用法。

静态用法

1、继承Fragment,重写onCreateView决定Fragemnt的布局

2、在Activity中声明此Fragment,就当和普通的View一样

首先是布局文件:fragment1.xml

可以看到,这个布局文件非常简单,只有一个LinearLayout,里面加入了一个TextView。我们再新建一个fragment2.xml :

然后新建一个类Fragment1,这个类是继承自Fragment的:

publicclassFragment1extendsFragment{@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState) {returninflater.inflate(R.layout.fragment1, container,false);      }  }

可以看到,在onCreateView()方法中加载了fragment1.xml的布局。同样fragment2.xml也是一样的做法,新建一个Fragment2类:

publicclassFragment2extendsFragment{@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState) {returninflater.inflate(R.layout.fragment2, container,false);      }  }

然后打开或新建activity_main.xml作为主Activity的布局文件,在里面加入两个Fragment的引用,使用android:name前缀来引用具体的Fragment:

最后新建MainActivity作为程序的主Activity,里面的代码非常简单,都是自动生成的:

publicclassMainActivityextendsActivity{@Overrideprotectedvoid onCreate(BundlesavedInstanceState) {super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);      }  }

现在我们来运行一次程序,就会看到,一个Activity很融洽地包含了两个Fragment,这两个Fragment平分了整个屏幕,效果图如下:

动态用法

上面仅仅是Fragment简单用法,它真正强大部分是在动态地添加到Activity中,那么动态用法又是如何呢?

还是在静态用法代码的基础上修改,打开activity_main.xml,将其中对Fragment的引用都删除,只保留最外层的LinearLayout,并给它添加一个id,因为我们要动态添加Fragment,不用在XML里添加了,删除后代码如下:

然后打开MainActivity,修改其中的代码如下所示:

publicclassMainActivityextendsActivity{@Overrideprotectedvoid onCreate(BundlesavedInstanceState) {super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);Displaydisplay = getWindowManager().getDefaultDisplay();if(display.getWidth() > display.getHeight()) {Fragment1fragment1 =newFragment1();              getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();          }else{Fragment2fragment2 =newFragment2();              getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();          }      }    }

看到了没,首先,我们要获取屏幕的宽度和高度,然后进行判断,如果屏幕宽度大于高度就添加fragment1,如果高度大于宽度就添加fragment2。动态添加Fragment主要分为4步:

1.获取到FragmentManager,在Activity中可以直接通过getFragmentManager得到。

2.开启一个事务,通过调用beginTransaction方法开启。

3.向容器内加入Fragment,一般使用replace方法实现,需要传入容器的id和Fragment的实例。

4.提交事务,调用commit方法提交。

现在运行一下程序,效果如下图所示:

要想管理 Activity 中的片段,需要使用 FragmentManager。要想获取它,需要 Activity 调用 getFragmentManager()。

使用 FragmentManager 执行的操作包括:

通过 findFragmentById()(对于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段

通过 popBackStack()将片段从返回栈中弹出

通过 addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器

也可以使用 FragmentManager 打开一个 FragmentTransaction,通过它来执行某些事务,如添加和删除片段。

Fragment通信

尽管 Fragment 是作为独立于 Activity的对象实现,并且可在多个 Activity 内使用,但Fragment 的给定实例会直接绑定到包含它的 Activity。具体地说,Fragment 可以通过 getActivity() 访问 Activity实例,并轻松地执行在 Activity 布局中查找视图等任务。如:

View listView = getActivity().findViewById(R.id.list);

同样地,Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通过从 FragmentManager 获取对 Fragment 的引用来调用Fragment中的方法。例如:

ExampleFragmentfragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

创建对 Activity 的事件回调

在某些情况下,可能需要通过与 Activity 共享事件。执行此操作的一个好方法是,在Fragment 内定义一个回调接口,并要求宿主 Activity 实现它。 当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他Fragment共享这些信息。

例如,如果一个新闻应用的 Activity 有两个Fragment ,一个用于显示文章列表(Fragment A),另一个用于显示文章(Fragment B)—,那么Fragment A必须在列表项被选定后告知 Activity,以便它告知Fragment B 显示该文章。 在本例中,OnArticleSelectedListener 接口在片段 A 内声明:

public staticclassFragmentAextendsListFragment{     public interfaceOnArticleSelectedListener{         public void onArticleSelected(UriarticleUri);    } }

然后,该Fragment的宿主 Activity 会实现 OnArticleSelectedListener 接口并替代 onArticleSelected(),将来自Fragment A 的事件通知Fragment B。为确保宿主 Activity 实现此界面,Fragment A 的 onAttach() 回调方法(系统在向 Activity 添加Fragment时调用的方法)会通过转换传递到 onAttach() 中的 Activity 来实例化 OnArticleSelectedListener 的实例:

public staticclassFragmentAextendsListFragment{OnArticleSelectedListenermListener;@Overridepublic void onAttach(Activityactivity) {super.onAttach(activity);try{             mListener = (OnArticleSelectedListener) activity;        }catch(ClassCastExceptione) {thrownewClassCastException(activity.toString() +" must implement OnArticleSelectedListener");        }     } }

如果 Activity 未实现界面,则片段会引发 ClassCastException。实现时,mListener 成员会保留对 Activity 的 OnArticleSelectedListener 实现的引用,以便Fragment A 可以通过调用 OnArticleSelectedListener 界面定义的方法与 Activity 共享事件。例如,如果Fragment A 是 ListFragment 的一个扩展,则用户每次点击列表项时,系统都会调用Fragment中的 onListItemClick(),然后该方法会调用 onArticleSelected() 以与 Activity 共享事件:

public staticclassFragmentAextendsListFragment{OnArticleSelectedListenermListener;@Overridepublic void onListItemClick(ListViewl,Viewv, int position, long id) {UrinoteUri =ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);        mListener.onArticleSelected(noteUri);    }}

Fragment是否很完美

因为Fragment是由FragmentManager来管理,每一个Activity有一个FragmentManager,管理着一个Fragment的栈,Activity是系统级别的,由系统来管理ActivityManager,栈也是系统范围的。而Fragment则是每个Activity范围内的,所以在使用Fragment的时候也有几点要注意。

同一个Activity中,只能有一个ID或TAG标识的Fragment实例。

这很容易理解,同一个范围内,有标识的实例肯定是要唯一才行(否则还要标识干嘛)这个在布局中经常犯错,在布局中写Fragment最好不要加ID或者TAG,否则很容易出现不允许创建的错误。我的原则是如果放在布局中,就不要加ID和TAG,如果需要ID和TAG就全用代码控制。创建新实例前先到FragmentManager中查找一番,这也正是有标识的意义所在。

一个Activity中有一个Fragment池,实例不一定会被销毁,可能会保存在池中。

这个跟第一点差不多。就好比系统会缓存Activity的实例一样,FragmentManager也会缓存Fragment实例,以方便和加速再次显示。

FragmentManager的作用范围是整个Activity,所以,某一个布局ID,不能重复被Fragment替换。

通常显示Fragment有二种方式,一种是层叠到某个布局上,或者把某个布局上面的Fragment替换掉,但是这个布局不能出现二次,比如布局A中有ID为id的区域,要显示为Fragment,此布局A,只能在一个Activity中显示一个,否则第二个id区域不能被Fragment成功替换。因为虽有二个ID布局的实例,但ID是相同的,对FragmentManager来说是一样的,它会认为只有一个,因为它看的是布局的ID,而不是布局的实例。

Fragment的生命周期反应Activity的生命周期。

Fragment在显示和退出时会走一遍完整的生命周期。此外,正在显示时,就跟Activity的一样,Activity被onPause,里面的Fragment就onPause,以此类推,由此带来的问题就是,比如你在onStart()里面做了一些事情,那么,当宿主Activity被挡住,又出现时(比如接了个电话),Fragment的onStart也会被高到,所以你要想到,这些生命周期不单单在显示和退出时会走到。

Fragment的可见性。

这个问题出现在有Fragment栈的时候,也就是说每个Fragment不知道自己是否真的对用户可见。比如现在是Fragment A,又在其上面显示了Fragment B,当B显示后,A并不知道自己上面还有一个,也不知道自己对用户不可见了,同样再有一个C,B也不知。C退出后,B依然不知自己已在栈顶,对用户可见,B退后,A也不知。也就是说Fragment显示或者退出,栈里的其他Fragment无法感知。这点就不如Activity,a被b盖住后,a会走到onStop(),同样c显示后,b也能通过onStop()感知。Fragment可以从FragmentManager监听BackStackState的变化,但它只告诉你Stack变了,不告诉你是多了,还是少,还有你处的位置。有一个解决方案就是,记录页面的Path深度,再跟Fragment所在的Stack深度来比较,如果一致,那么这个Fragment就在栈顶。因为每个页面的Path深度是固定的,而Stack深度是不变化的,所以这个能准确的判断Fragment是否对用户可见,当然,这个仅针对整个页面有效,对于布局中的一个区域是无效的。

Fragment的事件传递。

对于层叠的Fragment,其实就相当于在一个FrameLayout里面加上一堆的View,所以,如果处于顶层的Fragment没处理点击事件,那么事件就会向下层传递,直到事件被处理。比如有二个Fragment A和B,B在A上面,B只有TextView且没处理事件,那么点击B时,会发现A里的View处理了事件。这个对于Activity也不会发生,因为事件不能跨窗体传播,上面的Activity没处理事件,也不会传给下面的Activity,即使它可见。解决之法,就是让上面的Fragment的根布局吃掉事件,为每个根ViewGroup添加onClick=“true”。

与第三方Activity交互。与第三方交互,仍要采用Android的标准startActivityForResult()和onActivityResult()这二个方法来进行。但对于Fragment有些事情需要注意,Fragment也有这二个方法,但是为了能正确的让Fragment收到onActivityResult(),需要:

宿主Activity要实现一个空的onActivityResult(),里面调用super.onActivityResult()

调用Fragment#startActivityForResult()而不是用Activity的 当然,也可以直接使用Activity的startActivityForResult(),那样的话,就只能在宿主Activity里处理返回的结果了。

小结

在用法的代码部分参考郭神的博客,感觉郭神在代码讲解部分通俗易懂,看起来也方便。总之,在使用Fragment也有一些注意事项,不是那么完美的,虽然谷歌推荐我们用Fragment来代替Activity来使用,我们也确实这做了,现在基本主流的APP也都是少量Activity+很多Fragment,但也需要避免有些坑慎入。

参考地址

1,https://developer.android.com/guide/components/fragments.html

2,http://blog.csdn.net/guolin_blog/article/details/8881711

3,http://toughcoder.net/blog/2014/10/22/effective-android-ui-architecture

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容