[Android] Fragment use and analysis

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.0After the , we can use layoutIdthe Fragment constructor that will be used as a parameter, so that there is no need to override onCreateViewthe 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. Fragmenttransactions ( 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()ActivityFragmentManagerActivityFragment
  • 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 Activity FragmentManager; 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 ActivitygetFragmentManager()

2. The method of FragmentrequireFragmentManager()

3. The method of FragmentgetFragmentManager()

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 Tagthe 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()will Fragmentadd to the top of the container without removing a previously added Fragment
  • replace()will replace the one at the top of the containerFragment

If there is currently a FragmentAand add()one is added via the method FragmentB, the FragmentAwill still be active, so when the back button is pressed, FragmentAit will not be called onCreateView方法.

If you create FragmentBand replace the top one FragmentA, then FragmentAwill be removed from the container (FragmentA will execute onDestroy at this time), and FragmentBwill 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

FragmentManagerIt 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:

insert image description here

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, onCreateDialogthe 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;
}

Guess you like

Origin blog.csdn.net/yang553566463/article/details/124178198