Android中最最常用—Fragment基础篇最详解
- 2019 年 10 月 5 日
- 筆記
前言
各位花粉好久不见,本人还没有从假期综合征中恢复状态,但是想到还有你们在,所以我的动力就立刻被充满啦!一直跟着我们学习的花粉们肯定会好奇, Activity
虽然已经学会了,但是还是无法实现像微信或者某东、某宝一样做到切换展示的样式,或者有的小伙伴是在点击时手动去显示和隐藏不同的布局页面,可是根本无法实现所想要达到的交互效果,本期我们为大家重点介绍一下如何实现类似效果。
我们本期的主角— Fragment
作为 Android
最基本也最重要的基础概念之一,在我们日常的 Android
开发中经常会用到。本文就从 Fragment
的诞生开始,详细的介绍下 Fragment
的各个方面,包括其定义、使用、通信、生命周期等!
概念
Fragment
被称为碎片,是 Android
3.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.静态添加
在需要加载 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" />
2.动态添加
首先 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
。
常用方法
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
(当这个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
中分别重写各个生命周期的方法,通过打印日志的方式,更好的去理解生命周期的调用,这里就不再进行阐述。
获取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; } }
构造函数及数据传递
在项目中,最常见的数据传递就是通过 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
的实际项目使用示例。