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中的时候我们观察日志:
首先进入的时候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转场动画我会在后面继续来总结分享的。
网友评论