各位花粉好久不见,本人还没有从假期综合征中恢复状态,但是想到还有你们在,所以我的动力就立刻被充满啦!一直跟着我们学习的花粉们肯定会好奇, 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
的方式添加,缺点是一旦添加就不能在运行时删除。在需要加载 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
的实际项目使用示例。