Android Fragment基本使用

作者: Dracula716 | 来源:发表于2016-09-24 10:18 被阅读6010次

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

    相关文章

      网友评论

      本文标题:Android Fragment基本使用

      本文链接:https://www.haomeiwen.com/subject/vhrxettx.html