(自己个人见解,如有错误的地方,您可以评论,我会及时修改,感谢)

一、为什么要使用

        单纯好用,后续在补充...

        我是用来替换下面的常规方法

    // 替换 FrameLayout 中的 Fragment
    public void replaceFragment(Fragment fragment) {
        // 声音事件
        AudioPlayerUtil.getInstance().setOnAudioCompleteListener(null);
        AudioPlayerUtil.getInstance().stopAudio();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.setTransition(FragmentTransaction.TRANSIT_NONE); // 无动画
        transaction.replace(R.id.fragment_space, fragment); // 替换 FrameLayout 内容
        transaction.commit(); // 提交事务
    }

二、FragmentContainerView是什么

  FragmentContainerView 是一个专门用于托管 Fragment 的容器,强烈建议用于动态添加 Fragment 的场景,以替代 FrameLayout 或其他布局。(详细可以看看官网介绍)

三、基本使用

       -1、项目整体结构(没有使用MainActivity,而是使用FlashActivity当启动)

        

        0、配置工作

           依赖导入

    implementation(libs.androidx.navigation.fragment.ktx)
    implementation(libs.androidx.navigation.ui.ktx)


    androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationVersion" }
    androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationVersion" }

    navigationVersion = "2.6.0"


        打开viewbinding, 这个非常好用。使用这个就是为了避免什么findById, R.id 这些东西。

buildFeatures{
        viewBinding = true
}

        为什么使用了这个,下面的还是使用了R.id.xxxx, 因为这个 FragmentContainerView 有点特殊。

val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

      1、创建三个Fragment

        每一个Fragment都有一个按钮用来跳转下一个Fragment,像firstFragment调转SecondFragment, SecondFragment跳转ThirdFragment、ThirdFragment跳转FirstFragment。但是代码为什么报错了,继续往下看。导航组件还没有配置完。(findNavController().navigate(R.id.action_first_Fragment_to_secondFragment)

import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.example.androidktzero.R
import com.example.androidktzero.databinding.FragmentFirstBinding

/**
 * FirstFragment 代表导航中的第一个 Fragment
 * 用户可以点击按钮跳转到 SecondFragment
 */
class FirstFragment : Fragment(R.layout.fragment_first) { // 传入布局资源 ID,Fragment 会自动加载对应的 XML

    // ViewBinding 变量(用于访问 XML 视图)
    private var _binding: FragmentFirstBinding? = null

    // 只读属性,确保 _binding 不为空时才能使用
    private val binding get() = _binding!!

    /**
     * 当 Fragment 的视图创建完成时调用
     * @param view 生成的 View
     * @param savedInstanceState 之前保存的状态
     */
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 绑定 ViewBinding(必须使用 bind 方法,不能使用 inflate)
        _binding = FragmentFirstBinding.bind(view)

        // 设置点击事件,点击按钮后导航到 SecondFragment
        binding.goToSecond.setOnClickListener {
            findNavController().navigate(R.id.action_first_Fragment_to_secondFragment)
        }
    }

    /**
     * 当 Fragment 视图销毁时,将 _binding 置空,避免内存泄漏
     */
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

       为什么使用androidx.appcompat.widget.AppCompatButton 这个button而不使用 <Button>, 因为这个好方便控制Button样式。

<?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=".fragment.FirstFragment">

    <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/go_to_second"
            android:layout_gravity="center"
            android:text="跳转第二个"
            android:background="@color/md_blue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

</FrameLayout>

        页面效果就是下面这样

                

        2、Activity的界面

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".FlashActivity">

    <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:navGraph="@navigation/nav_graph"
            app:defaultNavHost="true"
            >

    </androidx.fragment.app.FragmentContainerView>

    <LinearLayout
            android:layout_gravity="bottom"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_marginBottom="50dp"
            >

        <androidx.appcompat.widget.AppCompatButton
                android:text="To_First"
                android:id="@+id/to_first"
                android:background="@drawable/button_background"
                android:layout_width="wrap_content"
                android:layout_height="match_parent">
        </androidx.appcompat.widget.AppCompatButton>

        <androidx.appcompat.widget.AppCompatButton
                android:layout_marginRight="50dp"
                android:layout_marginLeft="50dp"
                android:text="To_Second"
                android:id="@+id/to_second"
                android:background="@color/md_grey"
                android:layout_width="wrap_content"
                android:layout_height="match_parent">
        </androidx.appcompat.widget.AppCompatButton>

        <androidx.appcompat.widget.AppCompatButton
                android:text="To_Third"
                android:id="@+id/to_third"
                android:background="@color/md_grey"
                android:layout_width="wrap_content"
                android:layout_height="match_parent">
        </androidx.appcompat.widget.AppCompatButton>

    </LinearLayout>

</FrameLayout>

        效果图如下:

        

       为什么这样布局,是可以为了验证Fragment内部按钮跳转和Activity底部按钮控制跳转

       Activity代码如下:

       代码中  实现了 View.OnClickListener 接口,这个也好用。非常方便整合按钮点击事件。

package com.example.androidktzero  // 定义当前 Kotlin 文件的包名

import android.os.Bundle                                           // 导入 Android 组件的 Bundle 类
import android.view.View
import androidx.activity.enableEdgeToEdge                          // 导入启用 Edge-to-Edge 的扩展函数
import androidx.appcompat.app.AppCompatActivity                    // 导入 AndroidX 提供的 AppCompatActivity
import androidx.navigation.NavController                           // 导入 Navigation 组件的 NavController
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment                // 导入用于托管 Navigation 组件的 NavHostFragment
import com.example.androidktzero.databinding.ActivityFlashBinding  // 导入 ViewBinding 绑定的 ActivityFlashBinding

/**
 * `FlashActivity` 作为应用的启动 Activity,主要负责初始化导航组件 (Navigation Component)。
 * 该 Activity 主要加载 `activity_flash.xml` 布局,并初始化 `NavController` 进行 Fragment 导航控制。
 */
class FlashActivity : AppCompatActivity() , View.OnClickListener {

    // 使用 lateinit 关键字延迟初始化 ViewBinding 变量
    private lateinit var _binding: ActivityFlashBinding

    // 声明 NavController 变量用于控制 Navigation 组件的导航行为
    private lateinit var navController: NavController

    /**
     * `onCreate` 方法是 Activity 的生命周期方法之一,
     * 负责在 Activity 创建时进行 UI 初始化、ViewBinding 绑定以及 Navigation 组件的初始化。
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 启用 Edge-to-Edge UI,使界面能够扩展到状态栏和导航栏
        enableEdgeToEdge()

        // 使用 ViewBinding 绑定 `activity_flash.xml` 布局
        _binding = ActivityFlashBinding.inflate(layoutInflater)

        // 设置 Activity 的内容视图为绑定的根布局视图
        setContentView(_binding.root)

        // 获取 NavHostFragment  NavHostFragment 作为一个特殊的 Fragment 容器,它不直接暴露给 binding,所以你仍然需要使用
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

        // 获取 NavController,用于控制 Fragment 之间的导航
        navController = navHostFragment.navController

        // 设置按钮点击事件
        _binding.toFirst.setOnClickListener(this)
        _binding.toSecond.setOnClickListener(this)
        _binding.toThird.setOnClickListener(this)
    }

    override fun onClick(p0: View?) {
        val navOptions = NavOptions.Builder()
            .setLaunchSingleTop(true)  // 避免重复加载相同的 Fragment
//            .setPopUpTo(R.id.secondFragment, true)  // 清除栈中 firstFragment 之前的所有 Fragment
            .setPopUpTo(R.id.nav_host_fragment, true) // 清除所有 Fragments
            .build()
        when(p0?.id){
            R.id.to_first  -> {
                navController.navigate(R.id.firstFragment)
            }
            R.id.to_second -> {
                navController.navigate(R.id.secondFragment, null, navOptions)
            }
            R.id.to_third  -> {
                navController.navigate(R.id.thirdFragment, null, navOptions)
            }
        }
    }
}

     3、navigation的配置

        

        xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
            app:startDestination="@id/firstFragment"
        >
    <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.androidktzero.fragment.FirstFragment"
            android:label="FirstFragment"
            tools:layout="@layout/fragment_first"
            >
        <action
                android:id="@+id/action_first_Fragment_to_secondFragment"
                app:destination="@+id/secondFragment"
                ></action>
    </fragment>


    <fragment
            android:id="@+id/secondFragment"
            android:name="com.example.androidktzero.fragment.SecondFragment"
            android:label="SecondFragment"
            tools:layout="@layout/fragment_second"
            >
        <action
                android:id="@+id/action_secondFragment_to_thirdFragment"
                app:destination="@+id/thirdFragment"
                ></action>
    </fragment>

    <fragment
            android:id="@+id/thirdFragment"
            android:name="com.example.androidktzero.fragment.ThirdFragment"
            android:label="ThirdFragment"
            tools:layout="@layout/fragment_third"
            >

        <action
                android:id="@+id/action_thirdFragment_to_firstFragment"
                app:destination="@id/firstFragment"
                >
        </action>

    </fragment>

</navigation>

        效果图如下:

       

        博主 博主 为什么上面有线条呢?因为我提前配置了,跳转逻辑。<action> 指定了一个跳转行为,然后设置了一个跳转id。在哪里使用了呢,就在Fragment的点击事件中有体现。当然,也可以不用提前配置。

四、关于性能问题以及OOM等等(后续补上)

        想着,如果他是一个容器,那它就会被装满,装满就是异常。然后,怎么解决返回栈无限制的增大。我个人认为比较好的方法就是写一个Fragment的基类,用来管理Fragment的返回栈。至于这么实现下回说。

        虽然nav 自己也可以解决,如下:

override fun onClick(p0: View?) {
        val navOptions = NavOptions.Builder()
            .setLaunchSingleTop(true)  // 避免重复加载相同的 Fragment
//            .setPopUpTo(R.id.secondFragment, true)  // 清除栈中 firstFragment 之前的所有 Fragment
            .setPopUpTo(R.id.nav_host_fragment, true) // 清除所有 Fragments
            .build()
        when(p0?.id){
            R.id.to_first  -> {
                navController.navigate(R.id.firstFragment)
            }
            R.id.to_second -> {
                navController.navigate(R.id.secondFragment, null, navOptions)
            }
            R.id.to_third  -> {
                navController.navigate(R.id.thirdFragment, null, navOptions)
            }
        }
    }

 但是navOptions 好像并没有起多大作用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐