安卓Fragment使用详解

Fragment是在安卓3.0版本中添加的,主要是为了解决安卓设备尺寸多样化后界面的显示问题。Fragment比Activity较轻量级,也可以提供与用户交互的界面并且有自己的生命周期,也不用在Manifest.xml中注册但它必须嵌套在Activity中使用。之前需要使用多个Activity显示的内容,现在可以用一个Activity嵌套多个Fragment来实现。下面就来说说Fragment的知识点和使用注意事项。

一 Fragment的生命周期

先来一张google官网Fragment的生命周期图


安卓Fragment使用详解_第1张图片
fragment_liftcycle.png

Fragment的生命周期方法与Activity的类似但比Activity的稍微多了点,虽然多了点,对比着Activity的生命周期也是不难记忆滴。注意一点,Fragment的生命周期不是从new Fragment()开始的,而是从Fragment增加到Activity开始的。

二 Fragment的管理方式

Fragment需要嵌套在Activity中使用,Activity通过FragmentManager管理其拥有的Fragment。由于Fragment在3.0以后才有,为了兼容之前的版本google在V4包中添加了
android.support.v4.app.Fragment
android.support.v4.app.FragmentManager
android.support.v4.app.FragmentTransaction
需不需要兼容3.0以下版本根据产品来决定,但在开发过程中一定要保持统一,就是使用V4包下面的Fragment对应的FragmentManager,FragmentTransaction也使用V4包下面的,否则会编译报错。不同的管理方式会走Fragment不同的生命周期方法,下面来分别说下Activity通过FragmentManager管理Fragment的几种方式:

  1. replace(替换模式)
    所谓替换模式就是通过调用public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag)方法根据需求显示不同的Fragment,来看下google文档对这个方法的说明

Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here

大致意思就是说在容器(放Fragment的布局)中已经被add的Fragment都会被替换,相当于调用了remove方法,然后把新的Fragment添加到相同的容器中。
我们来通过实例演示下replace模式Fragment的生命周期,我们有两个Fragment 分别为BlankFragment1 和 BlankFragment2 ,通过MainActivity来管理它们,在MainActivity的onCreate方法中我们先Add BlankFragment1,代码如下:

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
blankFragment1 = new BlankFragment1();
blankFragment2 = new BlankFragment2();
Log.e(getClass().getSimpleName(),"<-------------------Add BlankFragment1------------------------>");
ft.add(R.id.fl_frame,blankFragment1,tag);
ft.commit();

这样当我们打开应用进入MainActivity,就会先把blankFragment1添加到Activity,此时打印blankFragment1的生命周期如下:

07-18 10:02:17.485 719-719/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Add BlankFragment1------------------------>
07-18 10:02:17.488 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onAttach
07-18 10:02:17.489 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onCreate
07-18 10:02:17.490 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onCreateView
07-18 10:02:17.492 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onActivityCreated
07-18 10:02:17.492 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onStart
07-18 10:02:17.493 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onResume

然后我们在MainActivity中添加两个按钮 btn_fragment2和btn_fragment1,点击各个按钮的代码逻辑如下:

@Override
public void onClick(View v) {
    int id = v.getId();
    if (id == R.id.btn_fragment2){
        Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
        if (fragment != null){
            Log.e(getClass().getSimpleName(),"<-------------------Replace BlankFragment1 to BlankFragment2------------------------>");
            getSupportFragmentManager().beginTransaction()
                        .replace(R.id.fl_frame,blankFragment2,tag2)
                        .commit();
            }
        }else if (id == R.id.btn_fragment1){
            Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag2);
            if (fragment != null){
                Log.e(getClass().getSimpleName(),"<-------------------Replace BlankFragment2 to BlankFragment1------------------------>");
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.fl_frame,blankFragment1,tag)
                        .commit();
            }
        }
    }

从代码中可以看到,当点击btn_fragment2时,通过replace()方法把当前已经增加到容器中的Fragment(其实就是blankFragment1)替换成blankFragment2;当点击btn_fragment1按钮时,把已经增加到容器中的Fragment(blankFragment2)替换成blankFragment1。在上面已经在onCreate()方法中Add了blankFragment1的基础上,下面我们来看看调用replace()方法Fragment的生命周期

  • 点击btn_fragment2
    当点击btn_fragment2时,blankFragment1被替换成blankFragment2,生命周期如下:
07-18 10:14:24.593 719-719/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Replace BlankFragment1 to BlankFragment2------------------------>
07-18 10:14:24.595 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onAttach
07-18 10:14:24.595 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onCreate
07-18 10:14:24.596 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onPause
07-18 10:14:24.596 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onStop
07-18 10:14:24.597 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onDestroyView
07-18 10:14:24.599 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onDestroy
07-18 10:14:24.599 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onDetach
07-18 10:14:24.600 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onCreateView
07-18 10:14:24.605 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onActivityCreated
07-18 10:14:24.606 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onStart
07-18 10:14:24.606 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onResume

可以看到blankFragment1销毁了,blankFragment2创建并显示。

  • 点击btn_fragment1
    当点击btn_fragment1时,blankFragment2被替换成blankFragment1,生命周期如下:
07-18 10:16:37.395 719-719/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Replace BlankFragment2 to BlankFragment1------------------------>
07-18 10:16:37.397 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onAttach
07-18 10:16:37.398 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onCreate
07-18 10:16:37.398 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onPause
07-18 10:16:37.398 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onStop
07-18 10:16:37.398 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onDestroyView
07-18 10:16:37.400 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onDestroy
07-18 10:16:37.401 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment2: onDetach
07-18 10:16:37.401 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onCreateView
07-18 10:16:37.407 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onActivityCreated
07-18 10:16:37.407 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onStart
07-18 10:16:37.407 719-719/com.jrmf360.fragmentliftcycle E/BlankFragment1: onResume

可以看到blankFragment2被销毁,blankFragment1被重新创建。
由此可见通过replace(替换模式),已经存在的Fragment会被完全销毁,替换的Fragment会被重新创建。这样每次用户点击都会重新加载页面有时并伴有大量的网络请求去获得数据,一般我们不会使用这种模式,除非需要每次加载最新数据。

  1. show(显示)hide(隐藏)模式
    在MainActivity的onCreate()方法中,我们先Add blankFragment1并把mCurrFragment设置为blankFragment1,代码如下:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
blankFragment1 = new BlankFragment1();
blankFragment2 = new BlankFragment2();
Log.e(getClass().getSimpleName(),"<-------------------Add BlankFragment1------------------------>");
ft.add(R.id.fl_frame,blankFragment1,tag);
ft.commit();
mCurrFragment = blankFragment1;

然后给按钮添加点击事件

@Override
public void onClick(View v) {
    int id = v.getId();
    if (id == R.id.btn_fragment2){
       if (blankFragment2.isAdded()){
            if (blankFragment2.isHidden()){
                Log.e(getClass().getSimpleName(),"<-------------------Hide BlankFragment1 Show BlankFragment2------------------------>");
                    getSupportFragmentManager().beginTransaction().hide(mCurrFragment).show(blankFragment2).commit();
                }
            }else{
                Log.e(getClass().getSimpleName(),"<-------------------Add BlankFragment2------------------------>");
                getSupportFragmentManager().beginTransaction().hide(mCurrFragment).add(R.id.fl_frame,blankFragment2).commit();
            }
            mCurrFragment = blankFragment2;
        }else if (id == R.id.btn_fragment1){
            if (blankFragment1.isAdded()){
                if (blankFragment1.isHidden()){
                    Log.e(getClass().getSimpleName(),"<-------------------Hide BlankFragment2 Show BlankFragment1------------------------>");
                    getSupportFragmentManager().beginTransaction().hide(mCurrFragment).show(blankFragment1).commit();
                }
            }else{
                Log.e(getClass().getSimpleName(),"<-------------------Add BlankFragment1------------------------>");
                getSupportFragmentManager().beginTransaction().hide(mCurrFragment).add(R.id.fl_frame,blankFragment1).commit();
            }
            mCurrFragment = blankFragment1;
        }
    }
  • 第一次点击btn_fragment2
    第一次点击btn_fragment2,此时blankFragment2并没有添加到容器,因此会先Add blankFragment2,日志信息如下:
07-18 10:50:16.755 4444-4444/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Add BlankFragment2------------------------>
07-18 10:50:16.757 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onAttach
07-18 10:50:16.757 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onCreate
07-18 10:50:16.758 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment1: onHiddenChanged:true
07-18 10:50:16.758 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onCreateView
07-18 10:50:16.764 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onActivityCreated
07-18 10:50:16.764 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onStart
07-18 10:50:16.764 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onResume

此时blankFragment2走正常的添加流程,但是我们看到日志中打印了 onHiddenChanged:true,这是因为我们在Add blankFragment2时,隐藏了blankFragment1,所以调用了blankFragment1的public void onHiddenChanged(boolean hidden)方法,下面来看下这个方法:

@Override
public void onHiddenChanged(boolean hidden) {
  super.onHiddenChanged(hidden);
  Log.e(getClass().getSimpleName(),"onHiddenChanged:" + hidden);
}

此方法当Fragment Hide或者show时调用,hidden 为true 表示当前Fragment隐藏。

  • 继续点击btn_fragment1
    此时blankFragment1,和blankFragment2都已经Add到了容器中,相关日志为:
07-18 10:50:20.722 4444-4444/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Hide BlankFragment2 Show BlankFragment1------------------------>
07-18 10:50:20.724 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment2: onHiddenChanged:true
07-18 10:50:20.726 4444-4444/com.jrmf360.fragmentliftcycle E/BlankFragment1: onHiddenChanged:false

可以看到并没有走Fragment的销毁和重新创建流程,而是把直接把blankFragment2隐藏并显示blankFragment1。此种方式可以很好的复用已经存在的Fragment。

  1. attach和detach模式
    我们常用的ViewPager就是使用这种模式来管理Fragment的,这种方式Fragment并不会完全销毁而仅仅是调用onDestroyView()方法来销毁view层级,然后需要该Fragment的时候也不会重新创建该Fragment而仅仅是从onCreateView()方法开始调用,重新构建该Fragment的view层级。下面贴一下FragmentPagerAdapter显示和销毁Fragment的源码
  • 显示Fragment
public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            //已经存在的Fragment直接attach
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            //不存在的Fragment先Add
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }
  • 销毁Fragment
@Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        //直接通过detach方法销毁Fragment的View层级
        mCurTransaction.detach((Fragment)object);
    }

为了验证结论我们用ViewPager加载三个Fragment分别为BlankFragment1,BlankFragment2,BlankFragment3且ViewPager的OffscreenPageLimit为1,这样当显示BlankFragment3时,BlankFragment1就会被detach,看下日志:

07-18 15:04:44.015 23543-23543/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Current Fragment is blankFragment3------------------------>
07-18 15:04:44.652 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onPause
07-18 15:04:44.653 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onStop
07-18 15:04:44.653 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onDestroyView

通过日志看出BlankFragment1调用了onDestroyView方法,销毁了view层级。然后我们在显示BlankFragment2,重新加载BlankFragment1,看下打印日志:

07-18 15:04:46.421 23543-23543/com.jrmf360.fragmentliftcycle E/MainActivity: <-------------------Current Fragment is blankFragment2------------------------>
07-18 15:04:46.853 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onCreateView
07-18 15:04:46.862 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onActivityCreated
07-18 15:04:46.863 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onStart
07-18 15:04:46.863 23543-23543/com.jrmf360.fragmentliftcycle E/BlankFragment1: onResume

可以看到BlankFragment1并没有新建而是从onCreateView方法开始执行。
以上就是Fragment的三种管理方式,我们根据具体的需求选择合适的方式,以达到事半功倍的效果。

三 Fragment的数据传递

Fragment的数据传递这个大家都比较熟悉了,这里就分三种情况简单说下。

  1. Activity向Fragment传递数据
  • 方式一
    Activity向Fragment传递数据如果是新建Fragment的时候我们可以通过Bundle传递,看一段google新建Fragment的代码
public static BlankFragment4 newInstance(String param1, String param2) {
BlankFragment4 fragment = new BlankFragment4();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}

在new Fragment的时候通过Bundle传递数据,并调用fragment.setArguments()方法给Fragment设置bundle。那么怎么获得bundle中的数据呢,继续看google的示例代码

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

通过getArguments()方法获得bundle,然后拿到bundle中的数据。

  • 方式二
    在Activity中通过调用getSupportFragmentManager().findFragmentByTag(tag)方法,获得对应的Fragment,拿到Fragment对象以后就可以调用该对象的方法去设置数据。
  1. Fragment向Activity传递数据
  • 方式一
    通过在Fragment中调用getActivity()方法获得与之绑定的Activity对象,拿到Activity对象之后就可以调用Activity中的方法传递数据。
  • 方式二
    通过回调接口的方式,这种方式也是google推荐的做法,继续看段google代码示例:
    /**
     * 在Fragment中创建接口
     */
    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(Uri uri);
    }
    
    /**
     * 在onAttach把传递过来的context也就是与Fragment关联的Activity
     * 给属性 OnFragmentInteractionListener mListener 赋值。这就需要
     * 对应的Activity实现OnFragmentInteractionListener接口
     * 
     * @param context
     */
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    /**
     * 当Fragment与对应的Activity解绑时释放mListener
     */
    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
  1. Fragment与Fragment传递数据
    在Fragment中调用 getActivity().getSupportFragmentManager().findFragmentByTag(tag),找到对应的Fragment对象,通过对象直接调用方法传递数据。

数据传递就简单介绍到这里,当然并不全面,欢迎各位大神补充。

四 创建Fragment并传递参数

Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().

英语太烂,翻译不到位的还请各位高台贵手。大致意思就是说:每个Fragment都必须有一个无参构造函数,当Fragment的状态发生变化重新初始化时可以调用该构造函数创建。并且强烈建议不要有其它的构造函数,因为这些构造函数在重新初始化的时候不会被调用,因此传递的参数也就丢失了。可以通过setArguments(Bundle)方式传递参数,当Fragment重新初始化的时候会从新获得bundle中传递的参数。
看了文档也就明白了我们要使用如下方式创建Fragment:

public static BlankFragment4 newInstance(String param1, String param2) {
BlankFragment4 fragment = new BlankFragment4();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}

拒绝通过构造函数传递参数的方式创建Fragment。

关于Fragment的知识就介绍到这里,如果有不对的地方还望各位大神指正~~~

你可能感兴趣的:(安卓Fragment使用详解)