Android Fragment基本使用


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:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="AFragment" />
</RelativeLayout>

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.xn.myproject.MainActivity">

    <fragment
        android:id="@+id/fragmenta"
        android:name="com.xn.myproject.fragment.AFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

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:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.xn.myproject.MainActivity">

    <FrameLayout
        android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

这里我们在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<Fragment> 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转场动画我会在后面继续来总结分享的。

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

推荐阅读更多精彩内容