Android Fragment use and analysis
Fragment was introduced in Android 3.0 (API level 11). Each Fragment has its own layout and life cycle.
Fragment cannot exist independently and must depend on Activity. There can be multiple Fragments in an Activity, and a Fragment can be reused by multiple Activities.
Basic usage of Fragment
First you need to create a Fragment, the code is as follows:
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class BlankFragment : Fragment(R.layout.fragment_blank) {
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
companion object {
fun newInstance(param1: String, param2: String) =
BlankFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Androidx fragment 1.1.0
After the , we can uselayoutId
the Fragment constructor that will be used as a parameter, so that there is no need to overrideonCreateView
the method , for example:class BlankFragment : Fragment(R.layout.fragment_blank) { // ...... }
Next, write the Activity code, and add a Fragment in onCreate(), the code is as follows:
class TestActivity : AppCompatActivity() {
private val binding by lazy(LazyThreadSafetyMode.NONE) {
ActivityTestBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// add Fragment;这是 fragment-ktx 为我们提供的扩展函数
supportFragmentManager.commit {
add(R.id.frag, BlankFragment.newInstance("p1", "p2"))
}
// add Fragment (传统的add Fragment方式)
/* supportFragmentManager.beginTransaction()
.add(R.id.fragmentContainerView, BlankFragment.newInstance("p1", "p2"))
.commit() */
}
}
The xml layout code corresponding to the Activity is as follows, in which a FragmentContainerView is written, which is:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TestActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Ok, through the above code, we can display the Fragment normally on the Activity.
Fragment container
In the past, we used FrameLayout as the container of Fragment. After AndroidX Fragment 1.2.0, we can use FragmentContainerView instead of FrameLayout .
FragmentContainerView is a View specially designed for Fragment, which inherits from FrameLayout.
At the same time, FragmentContainerView provides some properties that allow us to display Fragment in a more static code way,
- android:name: You can specify the Fragment that needs to be added on the container
- android:tag: tag can be set for Fragment
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.MyFragment"
android:tag="my_tag">
</androidx.fragment.app.FragmentContainerView>
FragmentManager
FragmentManager is responsible for managing all Fragments in Activity or Fragment, it is an abstract class used to create add, remove, replace, etc. Fragment
transactions ( Transaction
).
To dynamically add a Fragment, the first step is to obtain the FragmentManager.
So, how to get FragmentManager?
- Call the method of FragmentActivity : return , called when nesting is required.
getSupportFragmentManager()
Activity
FragmentManager
Activity
Fragment
- Call Fragment 's
getChildFragmentManager()
method :当前Fragment
Returned ,FragmentManager
called when nested is required.当前Fragment
子Fragment
- Call Fragment 's
getParentFragmentManager()
method : if the current Fragment is attached to the Activity, the method returns the ActivityFragmentManager
; if the current Fragment is a child Fragment of another Fragment, the ChildFragmentManager of its parent Fragment is returned.
Deprecated APIs:
1. The method of Activity
getFragmentManager()
2. The method of Fragment
requireFragmentManager()
3. The method of Fragment
getFragmentManager()
FragmentTranscation
In FragmentManager, all operations on Fragment are performed through FragmentTransaction.
Add/remove operations
add()
It is one of the many operations of Fragment, and there are methods at the same level as remove()
, replace()
, hide()
etc.,
add()
The first parameter is the id of the container hosting the Fragment, the second parameter is the Fragment object, and the third parameter is Tag
the name .
The advantage of specifying Tag is that we can get the Fragment object from FragmentManager through the following code:
val mFragment = supportFragmentManager.findFragmentByTag("my_fragment_tag")
In one transaction, you can do multiple operations, such as doing add()
// at remove()
the same time replace()
.
The remove() method will cause the Fragment's life cycle to execute onDetach, and then the Fragment instance will be removed from the FragmentManager.
The difference between add and replace
add()
willFragment
add to the top of the container without removing a previously added Fragmentreplace()
will replace the one at the top of the containerFragment
If there is currently a FragmentA
and add()
one is added via the method FragmentB
, the FragmentA
will still be active, so when the back button is pressed, FragmentA
it will not be called onCreateView方法
.
If you create FragmentB
and replace the top one FragmentA
, then FragmentA
will be removed from the container (FragmentA will execute onDestroy at this time), and FragmentB
will be on top.
Also, if our application has memory constraints, we need to consider using replace instead of add.
submit operation
For each transaction, we can commit()
submit it through the method.
commit()
Operations are asynchronous and are mManager.enqueueAction()
queued internally for processing.
commit()
The corresponding synchronization method is commitNow()
.
commit()
There will be operations inside checkStateLoss()
. If we use it improperly, for example, commit()
the operation onSaveInstanceState()
will throw an exception after the method, and commitAllowingStateLoss()
the method is commit()
a method that does not throw an exception version, but we should try to use it as much as possible commit()
to expose the problems in development in advance .
Add back stack
FragmentManager
It has a rollback stack (ie BackStack), which is similar to the task stack of Activity.
Through addToBackStack()
this method, the current transaction can be added to the rollback stack. When the user clicks the back button, the transaction will be rolled back.
Rollback means that if the transaction is add(), then the rollback operation is remove()
And if we don't add addToBackStack()
the method , the user will destroy it directly when he clicks the back button Activity
.
Fragment life cycle
Fragment has a total of 11 life cycle methods, as shown in the figure:
Let's take a look at what situations they correspond to and what they do:
1、onAttach(context: Context)
When the method is called back, it means that Fragment and Activity have been bound. At this time, we can execute the operation of mContext = context, and we can get the context of Activity normally.
2、onCreate(Bundle savedInstanceState)
It can be used to initialize Fragment, and the previous state can be obtained through the parameter savedInstanceState
3、onCreateView()
Initialize Fragment's layout, loading layout and FindViewById are usually done in this function. It is not recommended to perform time-consuming operations here.
4、onActivityCreated()
When this method is executed, the onCreate method of the Activity bound to the Fragment has been executed and returned, and UI operations that interact with the Activity can be performed in this method.
5、onStart()
Consistent with Activity, Fragment changes from invisible to visible when this method is executed.
6、onResume()
When this method is executed, the Fragment is active and the user can interact with it.
7、onPause()
When this method is executed, the Fragment is in a paused state, but it is still visible and the user cannot interact with it.
8、onStop()
When this method is executed, the Fragment is completely invisible.
9、onDestroyView()
Destroy the view related to the Fragment. Note that it is not unbound from the Activity at this time, and the view can still be recreated through the onCreateView method.
10、onDestroy()
Destroy the Fragment. This method is usually called when the Back key is pressed to exit or the Fragment is recycled.
11、onDetach()
Unbind with Activity.
So in some operation scenarios, how does its life cycle go? Let's see.
When calling show() 和 hide()
the method ,
Fragment的正常生命周期方法并不会被执行,此时仅仅是 Fragment 的 View 被显示或者隐藏
call add()
method
onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume
call remove()
method
onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
call replace()
method
其实就是执行上一个 Fragment 的 remove() 的生命周期 加上 下一个 Fragment 的 add() 的生命周期
detach()
When the method is called , Fragment
the life cycle is as follows:
onPause() -> onStop() -> onDestroyView()
// 注意,此时并不会执行 `onDestroy()` 和 `onDetach()` ,也就是说 Fragment 的实例还是存在的。
After calling attach()
the method , the life cycle is as follows:
onCreateView() -> onViewCreated() -> onActivityCreated() -> onStart() -> onResume()
From this we can also see the difference between add and attach:
Using the add() method to add a Fragment will trigger the onAttach() method,
Using the attach() method to add a Fragment will not trigger the onAttach() method.
When we press the home button to return to the desktop, the life cycle is as follows:
onPause() —> onStop()
When we return to Fragment from the desktop, the life cycle is as follows:
onStart() —> onResume()
Fragment communication problem
Method 1: setFragmentResultListener
In 2020, Google released new features of Android, including a new way of communication between Fragments, namely
- Send data using setFragmentResult
- Accept data using setFragmentResultListener
What are the advantages of this new approach? Include the following points:
- Pass data between Fragments, they don't hold references to each other
- Data is only processed when the life cycle is active, avoiding unknown problems that may occur when the Fragment is in an unpredictable state
- When the life cycle is ON_DESTROY, automatically remove the listener
- The event is sticky, which means that the message is sent first, and then the listener is created. The latest news can still be called back to the listener
Let's take a look at how this new communication method is used:
// 在 ActivityA 中发送数据
supportFragmentManager.setFragmentResult("request_key",
Bundle().also {
it.putInt("bundle_key", 1)
}
)
// 在 Fragment 中接受数据 ()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
parentFragmentManager.setFragmentResultListener(
"request_key",
viewLifecycleOwner,
object : FragmentResultListener {
override fun onFragmentResult(requestKey: String, result: Bundle) {
// 数据将会在这里回调
}
})
}
Note that viewLifecycleOwner will not be null only after the Fragment's view is created, so we put the logic of receiving data in the Fragment's onViewCreated method.
So, what is the use of viewLifecycleOwner passed in setFragmentResultListener?
We know that viewLifecycleOwner can perceive the life cycle of components, so we can easily think that the monitoring of receiving data will only get updated data when the Fragment is active . and destroy .
Method 2: ViewModel + LiveData
This method is also officially recommended by Google for communication between Fragments or with the host Activity.
We know that as long as the Activity is not destroyed, the ViewModel will always exist, and even if we switch between horizontal and vertical screens and the Activity is rebuilt, the ViewModel will not be affected.
An Activity can contain multiple Fragments, and these Fragments are independent of each other and belong to the same Activity. So we can use ViewModel and LiveData to realize communication between different Fragments in the same Activity .
Note that it is limited to the same Activity. Communication between Fragments of different activities cannot use this method.
How to use it is actually very simple. In the onViewCreated method of MyFragment, get the LiveData of the ViewModel of the host Activity, and start to observe the data update of this LiveData. as follows:
// MyFragment.kt:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProvider(requireActivity())[ActivityViewModel::class.java]
viewModel.value.observe(viewLifecycleOwner) {
// 在这里处理更新后的数据
}
}
Method 3: Interface Callback
The method of interface callback is slightly cumbersome, but it is also a way. Here is an example to illustrate (Fragment sends data to the host Activity):
// 1、创建一个接口,并定一个回调方法
interface CallbackListener {
fun callback(msg: String)
}
// 2、Activity 代码:
class ParentControlLockActivity : AppCompatActivity() {
// 在 Activity 中持有一个接口监听器
val listener = object : CallbackListener {
override fun callback(msg: String) {
// 在这里处理接收到的数据
}
}
// ......
}
// 3、Fragment 代码:
class BlankFragment : DialogFragment(R.layout.fragment_blank) {
private var mListener: CallbackListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// 获取宿主 Activity 的监听器
mListener = (activity as ParentControlLockActivity).listener
// 发送消息
mListener?.callback("Fragment发送消息")
}
}
other methods
- setArgments
- Handler
- broadcast
- EventBus
There are other communication methods, which are relatively simple, so I won’t give examples one by one here. Those who are interested can refer to other information.
DialogFragment
Based on Fragment, Android provides us with a better way to use dialog, namely DialogFragment, which inherits from Fragmet. DialogFragment can achieve all the needs of Dialog.
Using DialogFragment can better manage the life cycle of dialog. Compared with AlertDialog, AlertDialog lacks life cycle management. In addition, in special cases such as horizontal and vertical screen switching, using AlertDialog may bring many bugs.
Use of DialogFragment
First create a DialogFragment, the code is as follows:
private const val ARG_PARAM1 = "mTitle"
private const val ARG_PARAM2 = "mMessage"
class BlankFragment : DialogFragment() {
private lateinit var mBinding: DialogFragmentBinding
private var mTitle: String? = null
private var mMessage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.Theme_AppCompat_Dialog)
arguments?.let {
mTitle = it.getString(ARG_PARAM1)
mMessage = it.getString(ARG_PARAM2)
}
}
override fun onResume() {
super.onResume()
dialog?.window?.setLayout(
resources.getDimension(R.dimen.yd_cbc_gallery_dialog_w).toInt(),
resources.getDimension(R.dimen.yd_cbc_gallery_dialog_h).toInt()
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
mBinding = DialogFragmentBinding.inflate(inflater)
return mBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
private fun initView() {
mBinding.tvTitle.text = mTitle
mBinding.tvSub.text = mMessage
}
companion object {
// 展示 dialog
@JvmStatic
fun showDialog(fm: FragmentManager, param1: String, param2: String) {
BlankFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
show(fm, "BlankFragment")
}
}
}
}
When we need to display this dialog on the Activity, we only need to call the following code:
BlankFragment.showDialog(supportFragmentManager, "title", "msg")
In addition to the above usage of DialogFragment, there is another way to use it, that is, onCreateDialog
the method ,
private var mTitle: String? = null
private var mMessage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.Theme_AppCompat_Dialog)
arguments?.let {
mTitle = it.getString(ARG_PARAM1)
mMessage = it.getString(ARG_PARAM2)
}
}
// 重写 onCreateDialog 返回一个 dialog
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireContext()).setTitle(mTitle).setMessage(mMessage).create()
}
companion object {
// 展示 dialog
@JvmStatic
fun showDialog(fm: FragmentManager, param1: String, param2: String) {
BlankFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
show(fm, "BlankFragment")
}
}
}
}
When we show DialogFragment, we directly use the Fragment.show() method to display a Dialog, which actually uses the transaction submission method internally, which is the same as the ordinary Fragment.
// DialogFragment.java:
public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}