天天看点

碎片Fragment

https://www.jianshu.com/p/184f0c8857d6

安卓自3.0开始引入Fragment的概念,主要是为了能在不同分辩率屏幕上进行更为动态和灵活的UI设计,让程序更加合理和充分利用大屏幕空间。本篇将学习Fragment以下几个知识点:

  • Fragment概要
  • Fragment生命周期
  • 加载Fragment方法
    • 静态加载
    • 动态加载
  • Fragment与Activity之间通信

1.Fragment概要

学习Fragment的时候可以联系之前学习过的Activity,因为它们有很多相似点:都可包含布局,有自己的生命周期,Fragment可看似迷你活动。正如Fragment的名字--碎片,它的出现是为了解决Android碎片化 ,它可作为Activity界面的组成部分,可在Activity运行中实现动态地加入、移除和交换。一个Activity中可同时出现多个Fragment,一个Fragment也可在多个Activity中使用。活动和碎片像极了夫妻, 虽然紧密联系但是又有独立空间,在一起让彼此变得更好。

下面这个非常经典的例子更直观地说明了Fragment作用:

碎片Fragment

2.Fragment生命周期

先来看官方文档提供的有关Fragment生命周期的图片。

碎片Fragment

碎片Fragment

是不是能发现Fragment和Activity的生命周期太相似了,现在只需要再介绍几个Activity中没讲过的新方法:

碎片Fragment

在上图中画了几条线,可以看到Fragment周期中的状态几乎都是成对出现的,所以不难理解下图几种变化下Fragment生命周期方法的调用顺序了。

碎片Fragment

加载到Activity中的Fragment在各种变化下方法的调用顺序更值得注意。需要提一句的是,Activity的FragmentManager负责调用队列中Fragment的生命周期方法,只要Fragment的状态与Activity的状态保持了同步,托管Activity的FragmentManager便会继续调用其他生命周期方法以继续保持Fragment与Activity的状态一致。

碎片Fragment

Fragment生命周期与Activity生命周期的一个关键区别就在于,Fragment的生命周期方法是由托管Activity而不是操作系统调用的。Activity中生命周期方法都是protected,而Fragment都是public,也能印证了这一点,因为Activity需要调用Fragment那些方法并管理它。

3.加载Fragment方法

现在就来学习如何在Activity中加载Fragment。

(1)静态加载:在托管Activity的layout文件中声明Fragment

静态加载Fragment大致过程如图,分成四步:

碎片Fragment

下面通过一个简单的例子感受Fragment静态加载到Activity的过程。

第一步:新建frag_layout.xml,为Fragment指定一个布局,这里简单的放一个TextView和一个Button。

碎片Fragment

第二步:新建一个MyFragment类并继承Fragment,这里引用android.app包下的就可以,另一个包下主要用于兼容低版本的安卓系统。然后重写onCreateView()方法,这个方法里通过LayoutInflater的inflate()方法将刚刚定义的frag_layout布局加载进来并得到一个View,再return这个View。

碎片Fragment

第三步:新建mian.xml,作为Activity的布局,使用< fragment>标签添加碎片,并且一定要有android:name属性且值为被加载的Fragment类的包名.类名完整形式。

碎片Fragment

第四步:在MainActivity中加载main布局。现在MyFragment就完成了静态加载到MainActivity中,这时碎片里的控件自然也是活动的一个部分,可直接在活动中获取到Button的实例,来注册点击事件了。

碎片Fragment

运行一下看看能不能达到效果:

碎片Fragment

(2)动态加载:在托管Activity通过代码动态添加

动态加载的代码也非常简单,直接看例子。修改main.xml,将整个< fragment>删掉。但还保留一个LinerLayout的空间并且还给了Id,为何这样做?马上揭秘。

碎片Fragment

接下来在MainActivity中添加几行代码:

碎片Fragment

可将整个过程大致分为三个步骤:

第一步,先用getFragmentManager()方法获取一个FragmentManager对象,再通过它的beginTransaction()获取一个FragmentTransaction的实例。

第二步,用beginTransaction.add()方法将MyFragemnt实例添加到main布局里LinearLayout里,终于知道之前铺垫的Id是怎么回事了。一定要注意,add()方法里的第一个参数是容器视图资源Id,而不是layout。容器视图资源Id有两个作用:告知FragmentManager,碎片视图应该出现在活动视图的什么地方;它也是FragmentManager队列中碎片的唯一标识符。而静态加载时碎片的唯一标识符正是在活动布局里< fragment>下的id。

第三步:调用beginTransaction.commit()提交。另外,如果允许用户通过按下返回按键返回到前一个Fragment状态,在调用commit()之前先调用addToBackStack(true)方法。

这里注意到动态加载进来的Fragment里的控件并不能直接在活动中findViewById得到,那么如何实现点击效果呢,学完下一个知识点就有办法了。

4.Fragment与Activity之间通信

在活动中可以通过调用FragmentManager的findFragmentById()方法来得到相应碎片的实例,继而可以调用碎片里的方法。以上面demo举例,如果想得到静态加载碎片的实例,可在MainActivity添加代码如下:

MyFragment myFragment = (MyFragment)getFragmentManager(). findFragmentById(R.id.fragment);
           

如果想得到动态加载碎片的实例,代码如下:

MyFragment myFragment = (MyFragment)fragmentManager(). findFragmentById(R.id.layout);
           

在碎片中也可以通过调用getActivity()方法来得到和当前碎片相关联的活动实例,这样调用活动里的方法就变得轻而易举了。比如想在MyFragment得到MainActivity的实例:

MainActivity activity=(MainActivity)getActivity();
           

于是碎片和活动可以很方便地进行通信了。再想一想碎片和碎片之间如何进行通信?先在一个碎片中可以得到与它相关联的活动,然后再通过这个活动去获取另外一个碎片的实例,这样实现了不同碎片之间的通信了。

现在你有没有想到解决之前那个问题的办法呢?可以这样做,修改MyFragment,代码如下图所示:

碎片Fragment

现在按钮点击就又有响应了!其实在实际开发中,如果某一板块每次用到都需要相同的功能,就完全可以在碎片中实现需求,而不必在活动中重复代码,这样可以提高代码的复用性。

 Fragment如何实现类似 Activity栈的压栈和出栈效果的?

Fragment的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次 add的 Fragment和 replace的 Fragment,然后当我们点击 back按钮的时候会自动帮我们实现退栈操作。 

ViewPager+Fragment的左右滑动,如何实现Fragment的懒加载,Viewpager默认加载几个?

Viewpager默认加载3个。1个Activity里面可能会以Viewpager(或其他容器)与多个Fragment来组合使用,而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个Activity刚创建的时候就变成需要初始化大量资源。所以我们要进行懒加载。方法比较多,这里给大家提供两种方案:

方案 

1:在 Fragment里的 setUserVisibleHint,该方法用于告诉系统,这个 Fragment的 UI是否是可见的。所以我们只需要继承 Fragment并重写该方法,即可实现在 Fragment可见时才进行数据加载操作,即 Fragment的懒加载。实现代码: 

1.package fragment; 
2.import android.support.v4.app.Fragment; 
 


3./**
4.*Fragment懒加载
5.*/
6.public abstract class LazyFragment extends Fragment{
7.protected boolean isVisible;
8.@Override9.//frahment从不可见到完全可见的时候,会调用该方法
10.public void setUserVisibleHint(booleanisVisibleToUser){
11.super.setUserVisibleHint(isVisibleToUser);
12.if(getUserVisibleHint()){
13.isVisible=true;
14.onVisible();//可见
15.}else{
16.isVisible=false;
17.onInvisible();//不可见
18.}
19.}
20.//懒加载的方法,在这个方法里面我们为Fragment的各个组件去添加数据
21.protected abstract void lazyLoad();
22.protected void onVisible(){
23.     lazyLoad();
24.}
25.protected void onInvisible(){
26. }
27.}
使用:
1.public class Fragment extends LazyFragment{
2.privatebooleanisPrepared;//标志位,标志已经初始化完成。
3.//在这个方法里面去给我们的Fragment添加数据
4.@Override5.protectedvoidlazyLoad(){
6.if(isPrepared&&isVisible){
7.getNewsDate(getActivity(),channelId);
8.page++;
9.isPrepared=false;
10.}
11.}
12.@Override
13.public View 
 onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){
View 
 view=LayoutInflater.from(getActivity()).inflate(R.layout.fragment,container,false);
17.isPrepared=true;
   lazyLoad();//这里我们调用以下去加载我们的数据 

return view; 


20.}
           

方案 2:在继承 FragmentStatePagerAdapter重写以下的方法,返回 PagerAdapter.POSITION_NONE; 

//这个参数为不进行预加载[email protected](Objectobject){

returnPagerAdapter.POSITION_NONE;

}

4. Fragment与Activity之间是如何传值的:

1. Activity向Fragment传值:

步骤:

  1. 要传的值,放到bundle对象里;
  2. 在Activity中创建该Fragment的对象fragment,通过调用

    fragment.setArguments()

    传递到fragment中;
  3. 在该Fragment中通过调用

    getArguments()

    得到bundle对象,就能得到里面的值。

2. Fragment向Activity传值:

第一种:

在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id)

FragmentManager fragmentManager = getFragmentManager();

Fragment fragment = fragmentManager.findFragmentByTag(tag);

第二种:

通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中

5. Fragment与Fragment之间是如何传值的:

第一种:

通过

findFragmentByTag

得到另一个的Fragment的对象,这样就可以调用另一个的方法了。

第二种:

通过接口回调的方式。

第三种:

通过

setArguments

getArguments

的方式。

6. FragmentTransaction的add和replace的区别:

  1. add + hide + show的方式:

    其实add是一层层添加上去的,通过show去显示当前界面,hide去隐藏其他的界面,这时候的FrameLayout是会有很多层的。Fragment A 切换到Fragment B,然后再由Fragment B 切换到Fragment A 的时候,Fragment A 的所有生命周期是不会走的,只会调用

    onHiddenChanged(boolean isHidden)

    ,也就是说hide和show只是把其他界面隐藏,当前界面显示的效果,并不会走生命周期方法。
  2. replace的方式:

    其实replace是会替换掉原有的,所以这种方式的FrameLayout是只有一层的,再如上面的方式切换fragment,Fragment A会依次走生命周期方法:

    onAttach ---> onViewCreated ---> onActivityCreated ---> onStart。

    但是如果添加代码

    ft.addToBackStack(null)

    ,生命周期方法

    onAttach

    方法就不会走。

7. Fragment如何实现类似Activity的压栈和出栈效果的:

Fragment的事物管理器内部维持了一个双向链表结构,该结构可以记录我们add或者replace的Fragment,然后当我们按返回键的时候,会自动帮我们实现出栈操作。

8. FragmentPagerAdapter与FragmentStatePagerAdapter的区别:

一. 由于FragmentStatePagerAdapter在destoryItem的时候调用mCurTransaction.remove(fragment),会回收内存的,而页面比较多的时候,就比较消耗内存,所以FragmentStatePagerAdapter适合于页面比较多的情况。

二. FragmentPagerAdapter在destoryItem的时候调用mCurTransaction.detach(fragment),没有回收内存,只是将fragment与activity的UI进行分离,所以FragmentPagerAdapter适合于页面比较少的情况。

总结:FragmentPagerAdapter适用于页面较少的情况,FragmentStatePagerAdapter适用于页面较多的情况。

上一篇: Fragment碎片
下一篇: fragment(碎片)