Android中最最常用—Fragment基础篇最详解

  • 2019 年 10 月 5 日
  • 筆記

前言

各位花粉好久不见,本人还没有从假期综合征中恢复状态,但是想到还有你们在,所以我的动力就立刻被充满啦!一直跟着我们学习的花粉们肯定会好奇, Activity虽然已经学会了,但是还是无法实现像微信或者某东、某宝一样做到切换展示的样式,或者有的小伙伴是在点击时手动去显示和隐藏不同的布局页面,可是根本无法实现所想要达到的交互效果,本期我们为大家重点介绍一下如何实现类似效果。

我们本期的主角— Fragment作为 Android最基本也最重要的基础概念之一,在我们日常的 Android开发中经常会用到。本文就从 Fragment的诞生开始,详细的介绍下 Fragment的各个方面,包括其定义、使用、通信、生命周期等!

概念

Fragment被称为碎片,是 Android3.0(API 11)开始引入的组件,其初衷是便于大屏UI、平板电脑的设计和实现,现已广泛用于移动设备的开发中。

为了兼容低版本, support-v4库中也开发了一套 FragmentAPI,最低兼容Android 1.6,这也是我们在开发中建议使用的。

Fragment官方定义如下:

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

由以上定义可以看出:

  • Fragment是依赖于 Activity的,不能独立存在。
  • 一个 Activity里可以有多个 Fragment
  • 一个 Fragment可以被多个 Activity重用。
  • Fragment有自己的生命周期,并能接收输入事件。
  • 我们能在 Activity运行时动态地添加或删除 Fragment

基本使用

介绍 Fragment使用前,先介绍下 Fragment一些相关的核心类:

  • Fragment:基类,任何创建的 Fragment都需要继承该类。
  • FragmentManager:管理和维护 Fragment。它是一个抽象类,具体的实现类是 FragmentManagerImpl
  • FragmentTransaction:对 Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是 BackStackRecord

了解了以上概念后,先看下怎么创建一个 Fragment

public class MyFragment extends Fragment {        @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,                               Bundle savedInstanceState) {          // Inflate the layout for this fragment          return inflater.inflate(R.layout.fragment_my, container, false);      }  }

布局文件 R.layout.fragment_my代码如下:

<?xml version="1.0" encoding="utf-8"?>  <FrameLayout 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"      tools:context=".MyFragment">        <TextView          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_gravity="center"          android:text="我的Fragment"          android:textSize="20sp" />  </FrameLayout>

首先继承 Fragment,重写 onCreateView()方法,该方法返回 Fragment的UI布局。需要注意的是, inflate()的第三个参数需要设置为false,因为在 Fragment内部实现中,会把该布局添加到 container中,如果设为true,那么就会重复做两次添加,则会抛如下异常:

Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

因为 Fragment不能独立存在,需要依附于 Activity。把 Fragment添加到 Activity中的方式分为两种:

  • 静态添加:通过 xml的方式添加,缺点是一旦添加就不能在运行时删除。
  • 动态添加:运行时添加,这种方式比较灵活,因此建议使用这种方式。
1.静态添加

在需要加载 FragmentActivity对应的布局文件中添加 fragment的标签,需指定 name属性,为了限定类名。

需要注意的是,必须给 fragment的标签添加id属性,否则运行会报错。

<fragment      android:id="@+id/my_fragment"      android:name="com.phjt.fragmentdemo.MyFragment"      android:layout_width="match_parent"      android:layout_height="match_parent" />
2.动态添加

首先 Activity需要有一个容器存放 Fragment,一般是 FrameLayout,因此在 Activity的布局文件中加入 FrameLayoutActivity的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>  <androidx.constraintlayout.widget.ConstraintLayout      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"      tools:context=".MyFragmentActivity">        <FrameLayout          android:id="@+id/container"          android:layout_width="match_parent"          android:layout_height="match_parent" />    </androidx.constraintlayout.widget.ConstraintLayout>

然后直接在 Activity代码中添加:

//自定义的Fragment类  MyFragment myFragment = new MyFragment();  //要先获取FragmentManager对象  FragmentManager fragmentManager = getSupportFragmentManager();  //开启一个FragmentTransaction事务  FragmentTransaction transaction = fragmentManager.beginTransaction();  //添加Fragment到布局中  transaction.add(R.id.container, myFragment, "myFragment").commit();

这里我们需要注意几点:

  • 因为我们使用了 support库的 Fragment,因此需要使用 getSupportFragmentManager()获取 FragmentManager
  • add()是对 Fragment众多操作中的一种,还有 remove(), replace()等,第一个参数是根容器的id( FrameLayout的id,即”@+id/container”),第二个参数是 Fragment对象,第三个参数是 Fragment的tag名,指定tag的好处是后续我们可以通过 FragmentmFragment=getSupportFragmentManager().findFragmentByTag("myFragment")FragmentManager中查找 Fragment对象。
  • 在一次事务中,可以做多个操作,比如同时做 add().remove().replace()
  • commit()操作是异步的,内部通过 mManager.enqueueAction()加入处理队列。
  • addToBackStack("fname")是可选的。 FragmentManager拥有回退栈(BackStack),类似于 Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是 add(fragment),那么回退操作就是 remove(fragment));如果没添加该语句,用户点击返回按钮会直接销毁 Activity

常用方法

FragmentManager相关

1. getFragmentManager()

获取 Fragment父容器的管理器,现在该方法在 Activity中已经被标记过时,不推荐使用

2. getSupportFragmentManager()

V4包下的这个方法,与上一个效果一样,是 Android推荐使用的方法(可以兼容 Android所有版本)。

3. getChildFragmentManager()

Fragment可以添加到 Activity中,那么 Fragment是否也可以添加到 Fragment中呢?当然可以。当我们在 Fragment中继续添加 Fragment,怎么在父 Fragment中获取子 Fragment的管理器?就需要使用 getChildFragmentManager()来获取。

FragmentTransaction相关

可以看出 FragmentTransaction中的方法很多,下面介绍一些常用方法:

1. attach/detach()

  • detach(Fragmentfragment) :分离指定 Fragment的UI视图。
  • attach(Fragmentfragment) :重新关联一个 Fragment(当这个 Fragmentdetach执行之后)。

Fragmentdetach后, Fragment的生命周期执行完 onDestroyView就终止了,这意味着 Fragment的实例并没有被销毁,只是UI界面被移除了(注意和 remove是有区别的)。

Fragmentdetach后,执行 attach操作,会让Fragment从 onCreateView开始执行,一直执行到 onResume

attach无法像 add一样单独使用,单独使用会抛异常。方法存在的意义是对 detach后的 Fragment进行界面恢复。

2. add/remove()

这两个方法,应该是 Fragment中使用频率最高的两个方法了。add()remove()是将 Fragment添加和移除。remove()detach()要彻底一些,如果不加入到回退栈中, remove()的时候 Fragment的生命周期会一直走到 onDetach();如果加入了回退栈,则会只执行到 onDestoryView()Fragment对象还是存在的。

add一个 Fragment,如果加到的是同一个id的话,有点像我们的 Activity栈,启动多个 Activity时候, Activity一个个叠在上面, Fragment也是类似,一个个 Fragment叠在上面。

3. replace()

可以理解为先把相同id下的 Fragment移除掉,然后再加入这个当前的 Fragment。相当于 remove+add

4. hide/show()

如字面意思,让 Fragment隐藏和显示,可以类比 View的显示和隐藏。常常配合有多个 Fragment及有TAB等切换方式的时候,如APP的底部导航,选中某个按钮,让对应的 Fragment显示,其他 Fragment隐藏。

5. commit()

提交 Fragment,提交事务。额外补充:commit()方法并不立即执行 transaction中包含的动作,而是把它加入到UI线程队列中。如果想要立即执行,可以在 commit之后立即调用 FragmentManagerexecutePendingTransactions()方法。

6. addToBackStack()

FragmentTransaction中有加入回退栈的方法,但是没有退出的方法,因为这个方法在 FragmentManager中。

从图中可以看出, popBackStackFragmentTransaction是一个层级,所以 popBackStack操作的其实也是回退栈中 Fragment的事务( FragmentTransaction)。

生命周期

Fragment的生命周期和 Activity类似,但比 Activity的生命周期复杂一些,基本的生命周期方法如下图:

详细解释如下:

  • onAttach()FragmentActivity相关联时调用。可以通过该方法获取 Activity引用,还可以通过 getArguments()获取参数。
  • onCreate()Fragment被创建时调用。
  • onCreateView():创建 Fragment的布局。
  • onActivityCreated():当 Activity完成 onCreate()时调用。
  • onStart():当 Fragment可见时调用。
  • onResume():当 Fragment可见且可交互时调用。
  • onPause():当 Fragment不可交互但可见时调用。
  • onStop():当 Fragment不可见时调用。
  • onDestroyView():当 Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁 Fragment时调用。
  • onDetach():当 FragmentActivity解除关联时调用。

类比 Activity的生命周期和上图,可以在 ActivityFragment中分别重写各个生命周期的方法,通过打印日志的方式,更好的去理解生命周期的调用,这里就不再进行阐述。

获取Context

Fragment中,我们可以通过 getActivity()getContext()方法直接获取 Context

但有时候可能会获取为空,所以推荐使用以下方法获取 Context对象:

public class MyFragment extends Fragment {        private Context mContext;        //高版本,回调此方法      @Override      public void onAttach(Context context) {          super.onAttach(context);          mContext = context;      }        //API低于23的时候,回调这个方法      @Override      public void onAttach(Activity activity) {          super.onAttach(activity);          mContext = activity;      }  }

构造函数及数据传递

在项目中,最常见的数据传递就是通过 ActivityFragment传递参数。在上面动态添加 Fragment的时候,可以看出, Fragment就是一个普通的对象,可以通过 new的方式,所以可以通过构造函数或 set方法去给 Fragment传递参数,但并不推荐

推荐使用 Bundle来向 Fragment传递数据,在 Fragment通过 getArguments()获取参数。

public class MyFragment extends Fragment {        private String mName;        //不推荐使用      public MyFragment(String name) {          this.mName = name;      }        //推荐使用,bundle传递数据      public static MyFragment newInstance(String name) {          Bundle args = new Bundle();          args.putString("name", name);            MyFragment fragment = new MyFragment();          fragment.setArguments(args);          return fragment;      }        @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,                               Bundle savedInstanceState) {            //通过getArguments方法,获取传递的bundle数据          Bundle bundle = getArguments();          mName = bundle.getString("name");            return inflater.inflate(R.layout.fragment_my, container, false);      }  }

之所以使用 Bundle传递数据, Activity重新创建时,会重新构建它所管理的 Fragment,原先的 Fragment的字段值将会全部丢失。比如当横竖屏切换时, Fragment会调用自己的无参构造函数,在构造函数传参就会失效。若通过 Fragment.setArguments(bundle)方法设置的 Bundle数据就会保留下来,用于数据恢复,所以应尽量使用这个方式。

对于 Fragment之间的通信,由于 Fragment之间是没有任何依赖关系的,如果要进行 Fragment之间的通信,建议通过 Activity作为中介,不要 Fragment之间直接通信。

结语

以上就是今天 Fragment的基础内容介绍,内容还是挺多的,需要好好消化下。在接下来的文章里,将通过 RadioButtonFragment结合、 ViewPagerFragment相结合的例子,来详细介绍 Fragment的实际项目使用示例。

Exit mobile version