Android Fragment 基础

01.什么是Fragment

  • 什么是Fragment
    • 可以简单的理解为,Fragment是显示在Activity中的Activity。它可以显示在Activity中,然后它也可以显示出一些内容。因为它拥有自己的生命周期,可以接受处理用户的事件,并且你可以在一个Activity中动态的添加,替换,移除不同的Fragment,因此对于信息的展示具有很大的便利性。
    • 作为 view 界面的一部分,Fragment 的存在必须依附于 FragmentActivit使用,并且与 FragmentActivit 一样,拥有自己的独立的生命周期,同时处理用户的交互动作。同一个 FragmentActivit 可以有一个或多个 Fragment 作为界面内容,同样Fragment也可以拥有多个子Fragment,并且可以动态添加、删除 Fragment,让UI的重复利用率和易修改性得以提升,同样可以用来解决部分屏幕适配问题。
  • Fragment是组件还是控件
    • 严格意义上来说Fragment并不是一个显示控件,而只是一个显示组件。为什么这么说呢?其实像我们的Activity,Dialog,PopupWindow以及Toast类的内部都管理维护着一个Window对象,这个Window对象不但是一个View组件的集合管理对象,它也实现了组件的加载与绘制流程,而我们的Fragment组件如果看过源码的话,严格意义上来说,只是一个View组件的集合并通过控制变量实现了其特定的生命周期,但是其由于并没有维护Window类型的成员变量,所以其不具备组件的加载与绘制功能,因此其不能单独的被绘制出来,这也是我把它称之为组件而不是控件的原因。
  • 使用条件
    • 宿主Activity 必须继承自 FragmentActivity;
    • 使用getSupportFragmentManager() 方法获取 FragmentManager 对象;

02.Fragment生命周期

  • Fragment是依附于Activity存在的,因此它的生命周期收到Activity的生命周期影响
  • Fragment比Activity多了几个生命周期的回调方法
    • onAttach(Activity) 当Fragment与Activity发生关联的时候调用
    • onCreateView(LayoutInflater, ViewGroup, Bundle) 创建该Fragment的视图
    • onViewCreated(View view, Bundle savedInstanceState) 试图创建后调用该方法
    • onActivityCreated(Bundle) 当Activity的onCreated方法返回时调用
    • onDestroyView() 与onCreateView方法相对应,当该Fragment的视图被移除时调用
    • onDetach() 与onAttach方法相对应,当Fragment与Activity取消关联时调用
  • 注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现

03.Fragment使用

  • Activity创建Fragment的方式是什么?
    • 静态创建具体步骤
      • 首先我们同样需要注册一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。
    • 动态创建具体步骤
      • (1)创建待添加的碎片实例
      • (2)获取FragmentManager,在活动中可以直接通过调用 getSupportFragmentManager()方法得到。
      • (3)开启一个事务,通过调用beginTransaction()方法开启。
      • (4)向容器内添加或替换碎片,一般使用repalce()方法实现,需要传入容器的id和待添加的碎片实例。
      • (5)提交事务,调用commit()方法来完成。
  • 接下来就分别详细地理解一下两种创建fragment的方式的细节。

3.1 静态使用

  • 大概步骤:

    • ① 创建一个类继承Fragment,重写onCreateView方法,来确定Fragment要显示的布局
    • ② 在Activity中声明该类,与普通的View对象一样
  • 代码演示

    • 继承Frgmanet的类MyFragment【请注意导包的时候导v4的Fragment的包】
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyFragment extends Fragment {
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    /*
    * 参数1:布局文件的id
    * 参数2:容器
    * 参数3:是否将这个生成的View添加到这个容器中去
    * 作用是将布局文件封装在一个View对象中,并填充到此Fragment中
    * */
    View v = inflater.inflate(R.layout.item_fragment, container, false);
    return v;
    }
    }
    • Activity对应的布局文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <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.yczbj.fragment.MainActivity">

    <fragment
    android:id="@+id/search_fragment"
    android:name="com.yczbj.fragment.MyFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

    </LinearLayout>
    • Activity中的显示和隐藏fragment代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    phoneNumFragment = (PhoneNumFragment) getSupportFragmentManager()
    .findFragmentById(R.id.search_fragment);

    //处理返回键
    @Override
    public void onBackPressed() {
    if (phoneNumFragment != null && !phoneNumFragment.isHidden()) {
    hideSearch();
    return;
    }
    super.onBackPressed();
    }


    private void hideSearch() {
    getSupportFragmentManager()
    .beginTransaction()
    .hide(phoneNumFragment)
    .commit();
    }

    private void showSearch() {
    getSupportFragmentManager()
    .beginTransaction()
    .show(phoneNumFragment)
    .commit();
    }

3.2 动态使用

  • 代码如下所示,这种是平时开发最常用的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    OrderStatesFragment fragment = new OrderStatesFragment();
    Bundle bundle = new Bundle();
    bundle.putSerializable("confirmOrderModel",confirmOrderModel);
    fragment.setArguments(bundle);
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
    //transaction.show(fragment); //暂时不用这个
    transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
    transaction.commitAllowingStateLoss();
  • 部分代码说明

    • ① 穿件一个fragment的实例
    • ② 通过getSupportFragmentManager()方法新建Fragment管理器对象
    • ③ 然后通过Fragment管理器对象调用beginTransaction()方法,实例化FragmentTransaction对象,有人称之为事务
    • ④ FragmentTransaction对象【以下直接用transaction代替】,transaction的方法主要有以下几种:
      • transaction.add() 向Activity中添加一个Fragment
      • transaction.remove() 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁
      • transaction.replace() 使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体
      • transaction.hide() 隐藏当前的Fragment,仅仅是设为不可见,并不会销毁,它只会触发onHiddenChange()方法。
      • transaction.show() 显示之前隐藏的Fragment,它只会触发onHiddenChange()方法。
      • detach() 会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
      • attach() 重建view视图,附加到UI上并显示
      • ransatcion.commit() 提交事务
    • 注意:在add/replace/hide/show以后都要commit其效果才会在屏幕上显示出来

3.3 传递参数

  • 如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * Use this factory method to create a new instance of
    * this fragment using the provided parameters.
    *
    * @param param1 Parameter 1.
    * @param param2 Parameter 2.
    * @return A new instance of fragment FirstFragment.
    */
    // TODO: Rename and change types and number of parameters
    public static FirstFragment newInstance(String param1, String param2) {
    FirstFragment fragment = new FirstFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
    }

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

04.Fragment回退栈

  • Fragment的回退栈是用来保存每一次Fragment事务发生的变化

    • 如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。
  • 在某个activity上添加fragment,如果不处理宿主activity中返回键逻辑,点击返回键,关闭了fragment同时也关闭了activity。

    1
    2
    3
    4
    5
    6
    7
    OrderStatesFragment fragment = new OrderStatesFragment();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
    //transaction.show(fragment); //暂时不用这个
    transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
    transaction.commitAllowingStateLoss();
  • 在某个activity上添加fragment,如果不处理宿主activity中返回键逻辑,点击返回键,关闭了fragment,回到宿主activity页面。

    1
    2
    3
    4
    5
    6
    7
    8
    OrderStatesFragment fragment = new OrderStatesFragment();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
    //transaction.show(fragment); //暂时不用这个
    transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
    transaction.commitAllowingStateLoss();
    transaction.addToBackStack(null);

05.Fragment与Activity通信

  • Fragment依附于Activity存在,因此与Activity之间的通信可以归纳为以下几点:
    • 如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
    • 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作
    • Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作。不过不建议这样获取activity的实例,后面会提到的,容易报空指针异常问题。

06.Fragment与Activity通信的优化

  • 因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。

  • 针对直接创建fragment,一般可以这样写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    QrCodeLoginFragment fragment = new QrCodeLoginFragment();
    Bundle bundle = new Bundle();
    bundle.putString("unique_id", content);
    fragment.setArguments(bundle);
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(android.R.id.content, fragment, "qrCodeLoginFragment");
    transaction.commit();
    transaction.addToBackStack(null);
  • 针对有些频繁show或者hide的fragment,可以这样处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
    * 展示页面
    */
    private void showPlayingFragment() {
    if (isPlayFragmentShow) {
    return;
    }
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.setCustomAnimations(R.anim.fragment_slide_up, 0);
    if (mPlayFragment == null) {
    mPlayFragment = PlayMusicFragment.newInstance("Main");
    ft.replace(android.R.id.content, mPlayFragment);
    } else {
    ft.show(mPlayFragment);
    }
    ft.commitAllowingStateLoss();
    isPlayFragmentShow = true;
    }


    /**
    * 隐藏页面
    */
    private void hidePlayingFragment() {
    if(mPlayFragment!=null){
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.setCustomAnimations(0, R.anim.fragment_slide_down);
    ft.hide(mPlayFragment);
    ft.commitAllowingStateLoss();
    isPlayFragmentShow = false;
    }
    }

06.Fragment旋转场景

  • 在Activity的学习中都知道,当屏幕旋转时,是对屏幕上的视图进行了重新绘制。

    • 因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建,用脚趾头都明白…横屏和竖屏显示的不一样肯定是进行了重新绘制视图的操作。所以,不断的旋转就不断绘制,这是一种很耗费内存资源的操作,那么如何来进行优化?
  • 首先看看Fragment代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class FragmentOne extends Fragment {

    private static final String TAG = "FragmentOne";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    Log.e(TAG, "onCreateView");
    View view = inflater.inflate(R.layout.fragment_one, container, false);
    return view;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    Log.e(TAG, "onCreate");
    }

    @Override
    public void onDestroyView() {
    // TODO Auto-generated method stub
    super.onDestroyView();
    Log.e(TAG, "onDestroyView");
    }

    @Override
    public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    Log.e(TAG, "onDestroy");
    }

    }
    • 然后你多次翻转屏幕都会打印如下log
    1
    2
    3
    4
    5
    6
    07-20 08:18:46.651: E/FragmentOne(1633): onCreate  
    07-20 08:18:46.651: E/FragmentOne(1633): onCreate
    07-20 08:18:46.651: E/FragmentOne(1633): onCreate
    07-20 08:18:46.681: E/FragmentOne(1633): onCreateView
    07-20 08:18:46.831: E/FragmentOne(1633): onCreateView
    07-20 08:18:46.891: E/FragmentOne(1633): onCreateView
    • 因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。
  • 如何解决

    • 通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建
    • 默认的savedInstanceState会存储一些数据,包括Fragment的实例
    • 简单改一下代码,判断只有在savedInstanceState==null时,才进行创建Fragment实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MainActivity extends Activity {

    private static final String TAG = "FragmentOne";
    private FragmentOne mFOne;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    Log.e(TAG, savedInstanceState+"");
    if(savedInstanceState == null) {
    mFOne = new FragmentOne();
    FragmentManager fm = getFragmentManager();
    FragmentTransaction tx = fm.beginTransaction();
    tx.add(R.id.id_content, mFOne, "ONE");
    tx.commit();
    }
    }

    }
  • 现在无论进行多次旋转都只会有一个Fragment实例在Activity中,现在还存在一个问题,就是重新绘制时,Fragment发生重建,原本的数据如何保持?

    • 和Activity类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。

07.Fragment使用建议

  • 关于使用Fragment操作的使用建议
    • 如果Fragment视图被频繁的使用,或者一会要再次使用,建议使用show/hide方法,这样可以提升响应速度和性能。
    • 如果Fragment占用大量资源,使用完成后,可以使用replace方法,这样可以及时的释放资源。
  • 传递参数建议
    • Fragment的数据传递通过setArguments/getArguments进行,这样在Activity重启时,系统会帮你保存数据,这点和Activity很相似。

08.FragmentAdapter选择

  • FragmentPageAdapter和FragmentPageStateAdapter的区别?

    • FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响
    • FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存
  • 如何使用FragmentPageStateAdapter?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class BasePagerStateAdapter extends FragmentStatePagerAdapter {

    private List<?> mFragment;
    private List<String> mTitleList;

    /**
    * 接收首页传递的标题
    */
    public BasePagerStateAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) {
    super(fm);
    this.mFragment = mFragment;
    this.mTitleList = mTitleList;
    }

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

    @Override
    public int getCount() {
    return mFragment==null ? 0 : mFragment.size();
    }

    /**
    * 首页显示title,每日推荐等..
    * 若有问题,移到对应单独页面
    */
    @Override
    public CharSequence getPageTitle(int position) {
    if (mTitleList != null) {
    return mTitleList.get(position);
    } else {
    return "";
    }
    }
    }
  • 如何使用FragmentPagerAdapter?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    public class BasePagerAdapter extends FragmentPagerAdapter {

    private List<?> mFragment;
    private List<String> mTitleList;

    /**
    * 普通,主页使用
    */
    public BasePagerAdapter(FragmentManager fm, List<?> mFragment) {
    super(fm);
    this.mFragment = mFragment;
    }

    /**
    * 接收首页传递的标题
    */
    public BasePagerAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) {
    super(fm);
    this.mFragment = mFragment;
    this.mTitleList = mTitleList;
    }

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

    @Override
    public int getCount() {
    return mFragment==null ? 0 : mFragment.size();
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    }

    /**
    * 首页显示title,每日推荐等..
    * 若有问题,移到对应单独页面
    */
    @Override
    public CharSequence getPageTitle(int position) {
    if (mTitleList != null) {
    return mTitleList.get(position);
    } else {
    return "";
    }
    }

    public void addFragmentList(List<?> fragment) {
    this.mFragment.clear();
    this.mFragment = null;
    this.mFragment = fragment;
    notifyDataSetChanged();
    }

    }

    //具体使用
    FragmentManager supportFragmentManager = getSupportFragmentManager();
    BasePagerAdapter myAdapter = new BasePagerAdapter(supportFragmentManager,
    mFragments, mTitleList);
    vpContent.setAdapter(myAdapter);
    tabLayout.setupWithViewPager(vpContent);