Android development foundation - Fragment

In today's society, mobile devices are developing very rapidly. In addition to mobile phones, tablets are also slowly increasing. For tablets and mobile phones, the screen size and user habits are also different. For example, the screen size of a mobile phone is generally between 3 and 6 inches, and the screen size of a tablet is generally between 7 and 10 inches. There are many usage scenarios, while the tablet is mostly used in horizontal screen.

What is Fragment

Fragment is a kind of UI fragment that can be embedded in Activity, which can make the program more reasonable and make full use of the space of the large screen, so it is widely used on the tablet. At the same time, it can also contain layouts and has its own life cycle, which can be understood as another kind of Activity.

For example, for a video APP, on a mobile phone, there may be a video window on the top, followed by a video introduction, and a video list on the bottom. In the vertical screen state of the tablet, there may be no problem with this display, but when the tablet is placed horizontally, such The display scheme is not efficient enough for space utilization. Usually, the upper left corner is the video window, the lower left corner is the video introduction and comment area, and the entire right part is the video list. This method not only uses the screen space more efficiently , and more in line with human aesthetics.

In the horizontal state of the tablet mentioned above, you can put the left content and the right content in two Fragments respectively, and then introduce these two Fragments in the same Activity, so that you can make full use of the screen space.

How to use Fragment

First create a new FragementTest project.

Simple usage of Fragment

Then create a layout left_fragment.xml of the left Fragment:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- TODO: Update blank fragment layout -->
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button" />

</LinearLayout>

Then create a layout right_fragment.xml of the Fragment on the right:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#00ff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="24sp"
        android:text="This is right Fragment" />

</LinearLayout>

Then write the code in LeftFragment:

class LeftFragment:Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.left_fragment, container, false)
    }
}

 Then write the code in RightFragment:

class RightFragment:Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.right_fragment, container, false)
    }
}

The above code just dynamically loads the defined layout through the inflate method of LayoutInflater.

Then modify the code in activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        tools:ignore="Suspicious0dp"
        android:id="@+id/leftFrag"
        android:name="com.example.fragmenttest.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <fragment
        tools:ignore="Suspicious0dp"
        android:id="@+id/rightFrag"
        android:name="com.example.fragmenttest.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

</LinearLayout>

In the above code, the android:name attribute is also used to explicitly declare the Fragment class name to be added.

The result of running the program is:

Dynamically add Fragment

The above only adds Fragment to the layout file, but Fragment can also be dynamically added to the Activity when the program is running, so that the program interface can be customized more diversified.

Continue to create another_right_fragment.xml on the previous code:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#ffff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="24sp"
        android:text="This is another right fragment"/>

</LinearLayout>

 Here just modify the background color, and then create AnotherRightFragment:

class AnotherRightFragment:Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.another_right_fragment, container, false)
    }
}

Here also simply load the newly created layout, and then modify activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        tools:ignore="Suspicious0dp"
        android:id="@+id/leftFrag"
        android:name="com.example.fragmenttest.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <FrameLayout
        tools:ignore="Suspicious0dp"
        android:id="@+id/rightLayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

</LinearLayout>

 Here is to change the Fragment on the right to FrameLayout, and then modify the code in MainActivity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            replaceFragment(AnotherRightFragment())
        }
        replaceFragment(RightFragment())
    }

    private fun replaceFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction = fragmentManager.beginTransaction()
        transaction.replace(R.id.rightLayout, fragment)
        transaction.commit()
    }
}

 This will change the background color of the right Fragment after clicking the button in the left Fragment.

As can be seen from the above process, adding Fragment dynamically is mainly divided into 5 steps:

  • Create an instance of Fragment to be added
  • To get FragmentManager, you can directly call the getSupportFragmentManager method in the Activity to get it
  • Open a transaction by calling the beginTransaction method
  • Adding or replacing a Fragment to a container is generally implemented using the replace method, which needs to be passed in the id of the container and the Fragment instance to be added
  • Commit the transaction, use the commit method to complete

Implement the return stack in Fragment

In the above code, Fragment is dynamically added, but if you click the back button at this time, you will exit directly, but usually, the user may just want to return to the previous Fragment, which requires the implementation of the return stack.

There is an addToBackStack method in FragmentTransaction, which can be used to add a transaction to the return stack and modify the code in MainActivity:

    private fun replaceFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction = fragmentManager.beginTransaction()
        transaction.replace(R.id.rightLayout, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
    }

 Call the addToBackStack method before the transaction is committed, which can receive a name to describe the status of the return stack, and generally pass in null. After running the program, after clicking the button to switch the background, click the back button to return to the original background state, and then click the back button to exit the program.

Interaction between Fragment and Activity

Although Fragment can be embedded in Activity to display, in fact, the two have independent classes, and there is no obvious way to interact directly between the two.

In order to facilitate the interaction between the two, FragmentManager provides a method similar to findViewById, which is specially used to obtain Fragment instances from layout files. The code is:

    val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment

By calling the above method, you can get the instance of the corresponding Fragment in the Activity, and then you can call the method in the Fragment.

At the same time, kotlin-android-extensions also extends the findFragmentById method, allowing users to directly use the Fragment id name defined in the layout file to automatically obtain the corresponding Fragment instance:

    val fragment = leftFrag as LeftFragment

Obviously, this method is more concise.

On the contrary, in the Fragment, the Activity instance associated with the current Fragment can be obtained by calling the getActivity method:

        if(activity != null) {
            val mainActivity = activity as MainActivity
        }

Here, because the getActivity method may return null, it is necessary to perform null judgment processing, so that the Activity instance can be obtained.

For communication between different Fragments, you can first obtain the associated Activity in the Fragment, and then obtain another Fragment instance through the Activity, which indirectly realizes the communication between different Fragments.

Fragment life cycle

Fragment state and callbacks

Fragment, like Activity, also has several states in its life cycle:

  • Running state: When the Activity associated with a Fragment is running, the Fragment is also running.
  • Suspended state: When an Activity enters the suspended state (because another Activity that does not fill the screen is added to the top of the stack), the Fragment associated with it will enter the suspended state
  • Stopped state: When an Activity enters the stopped state, the Fragment associated with it will enter the stopped state, or the Fragment is removed from the Activity by calling the remove/replace method of FragmentTransaction, but the addToBackStack method is called before the transaction is committed. Fragment will also enter the stop state at this time. That is, Fragments that enter the stopped state are completely invisible to users and may be recycled by the system.
  • Destroyed state: Fragment always exists attached to the Activity, so when the Activity is destroyed, the Fragment associated with it will enter the destroyed state, or the Fragment will be removed from the Activity by calling the remove/replace method of FragmentTransaction, but in The addToBackStack method is not called before the transaction is committed, and the Fragment will also enter the destroyed state at this time.

Similar to Activity, Fragment also provides some additional callback methods to cover every link of its entire life cycle:

  • onAttach: Called when Fragment and Activity are associated
  • onCreateView: Called when creating a view (loading layout) for Fragment
  • onActivityCreated: Called when the Activity associated with the Fragment has been created
  • onDestroyView: Called when the view associated with the Fragment is removed
  • onDetach: Called when Fragment and Activity are disassociated

 Experience the life cycle of Fragment

Here is an example to look at the life cycle of Fragment:

class RightFragment:Fragment() {
    companion object {
        const val TAG = "RightFragment"
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        Log.d(TAG, "onAttach")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate")
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d(TAG, "onCreateView")
        return inflater.inflate(R.layout.right_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d(TAG, "onActivityCreated")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG, "onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }

    override fun onDetach() {
        super.onDetach()
        Log.d(TAG, "onDetach")
    }
}

The result of running the program is:

2022-09-24 11:33:33.318 4924-4924/com.example.fragmenttest D/RightFragment: onAttach
2022-09-24 11:33:33.319 4924-4924/com.example.fragmenttest D/RightFragment: onCreate
2022-09-24 11:33:33.322 4924-4924/com.example.fragmenttest D/RightFragment: onCreateView
2022-09-24 11:33:33.330 4924-4924/com.example.fragmenttest D/RightFragment: onActivityCreated
2022-09-24 11:33:33.331 4924-4924/com.example.fragmenttest D/RightFragment: onStart
2022-09-24 11:33:33.338 4924-4924/com.example.fragmenttest D/RightFragment: onResume

The order of printing information here is consistent with the Fragment life cycle shown in the above figure, and then click the button in the Fragment on the left:

2022-09-24 11:35:39.674 4924-4924/com.example.fragmenttest D/RightFragment: onPause
2022-09-24 11:35:39.674 4924-4924/com.example.fragmenttest D/RightFragment: onStop
2022-09-24 11:35:39.674 4924-4924/com.example.fragmenttest D/RightFragment: onDestroyView

The order of printing information here is also consistent with the Fragment life cycle shown in the above figure, and then click back:

2022-09-24 11:37:06.254 4924-4924/com.example.fragmenttest D/RightFragment: onCreateView
2022-09-24 11:37:06.262 4924-4924/com.example.fragmenttest D/RightFragment: onActivityCreated
2022-09-24 11:37:06.262 4924-4924/com.example.fragmenttest D/RightFragment: onStart
2022-09-24 11:37:06.262 4924-4924/com.example.fragmenttest D/RightFragment: onResume

The sequence of printing information here is also consistent with the Fragment life cycle shown in the figure above, and then click back to exit the program:

2022-09-24 11:37:49.956 4924-4924/com.example.fragmenttest D/RightFragment: onPause
2022-09-24 11:37:49.957 4924-4924/com.example.fragmenttest D/RightFragment: onStop
2022-09-24 11:37:49.958 4924-4924/com.example.fragmenttest D/RightFragment: onDestroyView
2022-09-24 11:37:49.961 4924-4924/com.example.fragmenttest D/RightFragment: onDestroy
2022-09-24 11:37:49.968 4924-4924/com.example.fragmenttest D/RightFragment: onDetach

The order of printing information here is also consistent with the Fragment life cycle shown in the above figure.

At the same time, in the Fragment, the onSaveInstanceState method can also be used to save data, because the Fragment that has entered the stop state may be recycled when the system memory is insufficient, and the saved data can be retrieved in the onCreate/onCreateView/onActivityCreated method, which contains a The savedInstanceState parameter of the Bundle type.

Techniques for Dynamically Loading Layouts

use qualifiers

In the tablet, many tablet applications use a double-page mode (a list with sub-items is displayed on the left, and the content is displayed on the right), because the tablet screen is large enough to display two pages of content at the same time, but the screen of the mobile phone only The content of one page can be displayed, so the two pages need to be displayed separately.

At this time, the qualifier is needed to determine whether the program should use double-page mode and single-page mode at runtime. Modify activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/leftFrag"
        android:name="com.example.fragmenttest.LeftFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

In the above code, only the Fragment on the left exists. Then create a new layout-large folder, and create a new activity_main.xml under this folder:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        tools:ignore="Suspicious0dp"
        android:id="@+id/leftFrag"
        android:name="com.example.fragmenttest.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"/>

    <fragment
        tools:ignore="Suspicious0dp"
        android:id="@+id/rightFrag"
        android:name="com.example.fragmenttest.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"/>

</LinearLayout>

In the above code, there are two Fragments, that is, double-page mode. Among them, large is a qualifier, the device whose screen is considered large will automatically load the layout under the layout-large folder, and the device with a small screen will still load the layout under the layout folder.

Then comment out the code in the replaceFragment method, and run the program on the tablet and mobile phone respectively:

 It can be seen that the result of dynamic loading of the layout when the program is running is different.

And some common qualifiers in Android have:

screen features qualifier describe
size small Resources provided for devices with small screens
normal Resources provided for medium screen devices
large Resources provided for large screen devices
xlarge Resources for devices with extra large screens
resolution ldpi Resources provided for low-resolution devices (below 120dpi)
mdpi Resources provided for medium resolution devices (120dpi~160dpi)
hdpi Resources provided for high resolution devices (160dpi~240dpi)
xhdpi Resources provided for ultra-high resolution devices (240dpi~320dpi)
xxhdpi Resources provided for ultra-ultra-high resolution devices (320dpi~480dpi)
direction land Resources provided for landscape devices
port Resources provided for portrait devices

Use min-width qualifier

The above uses the large qualifier to solve the judgment problem of single page and double page, but does not specify the specific threshold of large. And sometimes you want to be more flexible to load layouts for different devices, regardless of whether it is recognized as large by the system, you can use the minimum width qualifier at this time.

The minimum width qualifier allows the user to specify a minimum value (in dp) for the width of the screen, and then use the minimum value as the dividing point. Devices with a screen width greater than this value will load a layout, and devices with a screen width smaller than this value will Load another layout.

For example, if activity_main.xml is created under the layout-sw600dp folder, it will be loaded on devices with a screen width greater than or equal to 600dp, otherwise the default layout will be loaded.

Guess you like

Origin blog.csdn.net/SAKURASANN/article/details/127021115