Android Fragment 基本使用

1,722 阅读8分钟
原文链接: www.jianshu.com

0.背景

自从谷歌在Android3.0退出Fragment以后,Fragment就成为了绝大多数APP的必备元素,其重要成都一点也不亚于四大组件。从字面上来看,Fragment的意思是碎片,谷歌的本意在于将一个Activity的界面进行碎片化,好让开发者根据不同的屏幕来进行不同的Fragment组合以来达到动态布局的效果。但从目前的情况来看,因为Android平板电脑的市场占有率偏低,多数应用都未对平板进行单独适配,即使有适配的APP也是单独维护一个平板的项目与手机项目剥离开来进行UI的编写与适配。但是这并没有影响到广大开发者对Fragment的喜爱,因为fragment作为一个UI界面的载体,它的使用上十分灵活。同时更重要的一点是,同样实现一个界面,Fragment相对于Activity来说更加省内存,可以说它是一个更加轻量级的界面载体。如果说我们的应用里有一百个界面,如果全用Activity来进行实现的话,那么整个应用跑起来以后内存的消耗是极大的,而如果采用Activity+Fragment的实现方式,则可大大降低内存的消耗。所以Fragment在Android开发者当中是十分收欢迎的,但是欢迎归欢迎,Fragment中的各种坑也是另我在最初开发之时头疼不已。所以紧接着上一次对Activity的总结,这一次主要对Fragment一些基础知识进行一些总结和归纳。
注:以下的所有分析均不考虑向Android4.0以下兼容。

1.Fragment的基本使用

(1)将Fragment当作控件使用

这种方法是使用Fragment的最简单的一种方式了,我们只需要声明一个类继承自Fragment实现其onCreateView方法,并将fragment声明在Activity的xml里即可。我们来看代码:
AFragment.java:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
  return inflater.inflate(R.layout.afragment, container, false);
}

afragment.xml:



    

activity_main.xml:




    


AFragment显示效果.png

这样我们就将AFragment作为一个控件显示出来了,十分简单,只是需要注意fragment控件一定要加id属性即可,否则会崩溃。。但是将Fragment作为一个控件来使用绝对是杀鸡用牛刀的做法,不值得提倡,所以在这里也不对它做过多的分析了。

(2)FragmentManager动态加载Fragment

在代码中通过FragmentManager获取FragmentTransaction来进行Fragment的动态添加才是我们最常用的使用方式。先来看代码:
MainActivity:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mAFragment = new AFragment();

        getFragmentManager().beginTransaction()
                .replace(R.id.main_container, mAFragment).commit();
        getFragmentManager().beginTransaction().show(mAFragment);
    }

activity_main.xml:




    

这里我们在Activity中获取FragmentManager然后再进一步获取到FragmentTransaction对象将我们new出来的AFragment add到FrameLayout中。效果图跟上面第一种实现方式一致我就不贴图了。
这种动态加载Fragment的方式十分灵活,可以让我们在代码当中动态的决定加载哪些Fragment显示出来。这里我们需要重点关注的是FragmentTransaction对象。除了例子当中使用的add操作以外,它还有replace,hide,show,remove等操作,下面就对这几种方法一一进行解释。

1)add(int containerViewId, Fragment fragment)

这个方法是将fragmen添加到我们指定id的layout中.

2)hide(Fragment fragment)和show(Fragment fragment)

隐藏或者显示指定的fragment,类似于我们在View中经常使用的setVisibly方法,需要注意的是,这里的hide和show仅仅只是让fragment显示和隐藏,不会对fragment进行销毁,甚至我们在hide的时候fragment的onPause方法都没有被调用。

3)remove(Fragment fragment)

会将fragment移除,如果被移除的Fragment没有添加到回退栈,该Fragment会同时被销毁。

4)replace(int containerViewId, Fragment fragment)

replace方法是用来进行替换的,实际上也就是对指定的layout id先remove掉其fragment,然后再add上去我们指定的fragment的一种组合操作。

5)detach()

会将view从UI中移除,和remove()不同,此时fragment并没有与Activity断绝关系,所以生命周期的onDestroy方法和onDetach方法并没有被调用

6)attach()

重建view视图,附加到UI上并显示,如果调用完detach方法后再来调用该方法的话不会去走onAttach和onCreate方法。

需要注意的是,我们在进行了上述的各种操作以后一定要调用commit方法提交事务才能生效。虽然我没有研究过源码里针对这一段是怎样实现的,但是可以类比数据库的事务操作。当系统因为某种不可抗力而终端了操作就需要进行回滚,至于不回滚会发生什么,需要日后深入源码中进行研究才能得知,在这里我就不胡乱进行猜测免得误导大家。

2.Fragment生命周期


activity_fragment_lifecycle.png


如上图所示我们可以看到Fragment生命周期对应Activity的各个生命周期方法,在Activity中我已经对各生命周期方法进行了详细的解释,这里就不再对重复的内容进行解释,大家可以去看我的Android Activity全面解析。这里单独针对Fragment中一些特有的方法来进行说明。

1).onAttach

当该Fragment与Activity发生关联的时候调用,注意的是这个方法里会给我们传入一个Context上下文参数,此时我们可以将其存入成员变量中进行使用,避免在代码中调用getActivity()出现的空指针异常。

2).onCreate

当创建Fragment的时候调用与onAttach方法是一起调用的,如果没有调用到onAttach方法就不会调用该方法。

3).onCreateView

每次创建、绘制Fragment的View时调用,并且返回一个view对象。

4).onActivityCreated

当Fragment所在的Activity被onCreate完成时调用。

5)onDestoryView()

与onCreateView想对应,当该Fragment的视图被移除时调用。

6)onDestroy()方法

与onCreate想对应当Fragment的状态被销毁的时候进行调用。

6)onDetach()

与onAttach相对应,当Fragment与Activity关联被取消时调用,需要注意的是我们调用detach方法的时候并不会调用到该生命周期方法。

3.Fragment的典型应用场景

Fragment的应用场景最多的便是ViewPager+Fragment的实现,现在主流的APP几乎都能看到它们的身影,那么这一部分我就主要针对该应用场景进行分析。
ViewPager+Fragment结构
相信绝大多数的人都用ViewPager+Fragment的形式实现过界面,同时目前市面上主流的APP也都是采用这种结构来进行UI架构的,所以我们有必要单独对这种情况拿出来做一下分析。
首先我们来看一段代码:

public class MainActivity extends FragmentActivity
        implements
            View.OnClickListener {

    private ViewPager mViewPager;

    private List mList;
    private Fragment mOne;
    private Fragment mTwo;
    private Fragment mThree;
    private Fragment mFour;

    private Button mOneButton;
    private Button mTwoButton;
    private Button mThreeButton;
    private Button mFourButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mViewPager = (ViewPager) findViewById(R.id.content_pager);
        //加载Fragment
        mList = new ArrayList<>();
        mOne = new OneFragment();
        mTwo = new TwoFragment();
        mThree = new ThreeFragment();
        mFour = new FourFragment();
        mList.add(mOne);
        mList.add(mTwo);
        mList.add(mThree);
        mList.add(mFour);

        mOneButton = (Button) findViewById(R.id.one);
        mTwoButton = (Button) findViewById(R.id.two);
        mThreeButton = (Button) findViewById(R.id.three);
        mFourButton = (Button) findViewById(R.id.four);

        mOneButton.setOnClickListener(this);
        mTwoButton.setOnClickListener(this);
        mThreeButton.setOnClickListener(this);
        mFourButton.setOnClickListener(this);

        //设置到ViewPager中
        mViewPager.setAdapter(new ContentsPagerAdapter(
                getSupportFragmentManager()));

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.one :
                mViewPager.setCurrentItem(0);
                break;
            case R.id.two :
                mViewPager.setCurrentItem(1);
                break;
            case R.id.three :
                mViewPager.setCurrentItem(2);
                break;
            case R.id.four :
                mViewPager.setCurrentItem(3);
                break;
        }
    }


    class ContentsPagerAdapter extends FragmentStatePagerAdapter {

        public ContentsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return mList.get(position);
        }

        @Override
        public int getCount() {
            return mList.size();
        }
    }
}

在这里我们加载了4个Fragment到ViewPager中,同时我在这里使用的是
FragmentStatePagerAdapter。这里需要提到的是FragmentStatePagerAdapter与FragmentPagerAdapter的区别。
FragmentPagerAdapter:对于不再需要的fragment,仅仅只会调用到onDestroyView方法,也就是仅仅销毁视图而并没有完全销毁Fragment。
FragmentStatePagerAdapter:会销毁不再需要的fragment,一直调用到onDetach方法失去与Activity的绑定。销毁时,会调用onSaveInstanceState(Bundle outState)方法通过bundle将信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,我们可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。下面我们来看一下日志就清楚了:
首先在使用FragmentPagerAdapter中的时候我们观察日志:


FragmentPagerAdapter.png


首先进入的时候ViewPager处于第一个Fragment上,此时由于ViewPager的预加载功能TwoFragment也被加载了,通过日志我们就能看到。当我们此时切换到了第四个Fragment中去的时候,我们就会发现OneFragment仅仅只是调用了onDestroyView方法而已,后面的onDestroy方法很onDetach方法都没被调用到。


FragmentStatePagerAdapter.png


同样的操作再在FragmentStatePagerAdapter里来一遍,我们会发现当我们切换的时候One和Two的Fragment的onDestroyView,onDestroy,onDetach全部都调用到了。同时我在OnewFragment通过onSaveInstanceState方法存起来的值在下一次的onCreate的时候也能读取到。

通过上面的代码和例子我们基本搞清楚了ViewPager与Fragment如何结合起来使用,以及他们的生命周期调用我们也弄清楚了。那么我们到底什么时候用FragmentStatePagerAdapter,什么时候用FragmentPagerAdapter呢?根据我的经验来看,当页面较少的情况下可以考虑使用FragmentPagerAdapter,通过空间来换取时间上的效率。但当页面多了的时候我们就更需要使用FragmentStatePagerAdapter来做了,因为没有哪个用户希望某个应用会占爆它内存。

结束语

这一篇文章主要是是针对Fragment的一些基础问题进行一个全面总结,还有一些关于Fragment回退栈,Fragment嵌套,Fragment转场动画我会在后面继续来总结分享的。