各位花粉好久不见,本人还没有从假期综合征中恢复状态,但是想到还有你们在,所以我的动力就立刻被充满啦!一直跟着我们学习的花粉们肯定会好奇, 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的方式添加,缺点是一旦添加就不能在运行时删除。在需要加载 Fragment的 Activity对应的布局文件中添加 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" />首先 Activity需要有一个容器存放 Fragment,一般是 FrameLayout,因此在 Activity的布局文件中加入 FrameLayout。Activity的布局文件如下:
<?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。1. getFragmentManager():
获取 Fragment父容器的管理器,现在该方法在 Activity中已经被标记过时,不推荐使用。
2. getSupportFragmentManager():
V4包下的这个方法,与上一个效果一样,是 Android推荐使用的方法(可以兼容 Android所有版本)。
3. getChildFragmentManager():
Fragment可以添加到 Activity中,那么 Fragment是否也可以添加到 Fragment中呢?当然可以。当我们在 Fragment中继续添加 Fragment,怎么在父 Fragment中获取子 Fragment的管理器?就需要使用 getChildFragmentManager()来获取。

可以看出 FragmentTransaction中的方法很多,下面介绍一些常用方法:
1. attach/detach():
detach(Fragmentfragment) :分离指定 Fragment的UI视图。attach(Fragmentfragment) :重新关联一个 Fragment(当这个 Fragment的 detach执行之后)。当 Fragment被 detach后, Fragment的生命周期执行完 onDestroyView就终止了,这意味着 Fragment的实例并没有被销毁,只是UI界面被移除了(注意和 remove是有区别的)。
当 Fragment被 detach后,执行 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之后立即调用 FragmentManager的 executePendingTransactions()方法。
6. addToBackStack():
FragmentTransaction中有加入回退栈的方法,但是没有退出的方法,因为这个方法在 FragmentManager中。

从图中可以看出, popBackStack与 FragmentTransaction是一个层级,所以 popBackStack操作的其实也是回退栈中 Fragment的事务( FragmentTransaction)。
Fragment的生命周期和 Activity类似,但比 Activity的生命周期复杂一些,基本的生命周期方法如下图:

详细解释如下:
onAttach(): Fragment和 Activity相关联时调用。可以通过该方法获取 Activity引用,还可以通过 getArguments()获取参数。onCreate(): Fragment被创建时调用。onCreateView():创建 Fragment的布局。onActivityCreated():当 Activity完成 onCreate()时调用。onStart():当 Fragment可见时调用。onResume():当 Fragment可见且可交互时调用。onPause():当 Fragment不可交互但可见时调用。onStop():当 Fragment不可见时调用。onDestroyView():当 Fragment的UI从视图结构中移除时调用。onDestroy():销毁 Fragment时调用。onDetach():当 Fragment和 Activity解除关联时调用。类比 Activity的生命周期和上图,可以在 Activity和 Fragment中分别重写各个生命周期的方法,通过打印日志的方式,更好的去理解生命周期的调用,这里就不再进行阐述。
在 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;
}
}在项目中,最常见的数据传递就是通过 Activity向 Fragment传递参数。在上面动态添加 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的基础内容介绍,内容还是挺多的,需要好好消化下。在接下来的文章里,将通过 RadioButton与 Fragment结合、 ViewPager与 Fragment相结合的例子,来详细介绍 Fragment的实际项目使用示例。