【Android】Navigation的使用

一、背景

Google推出了FragmentContainerView用來(lái)替代FragmentManager進(jìn)行Fragment管理。

--

二、使用步驟

  1. 在項(xiàng)目根目錄build.gradle文件的中,添加如下代碼
buildscript {
    ...
    dependencies {
        ...
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0"
    }
}

在新版的AndroidStudio中,Gradle升級(jí)到7.2+后,可以將如下代碼添加到根目錄的build.gradle文件的頂頂部

buildscript {
    dependencies {
        classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0'
    }
}
  1. 在module/build.gradle文件中,添加apply plugin: "androidx.navigation.safeargs.kotlin",否則不能生成XXXDirections類(lèi);

  2. 在用到FragmentContainerView的Module中,引用navigation庫(kù),代碼如下:

    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
  1. 在Module的res/navigation目錄下創(chuàng)建對(duì)應(yīng)的導(dǎo)航圖表文件,如nav_main_graph.xml

  2. 創(chuàng)建Activity及對(duì)應(yīng)布局文件,并在布局文件中使用FragmentContainerView組件

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container_view"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/main_nav_graph" />
  1. 創(chuàng)建對(duì)應(yīng)的Fragment及布局,如:LoginFragment、HomeFragment,并在導(dǎo)航圖表文件nav_main_graph.xml中添加對(duì)應(yīng)Fragment
<?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"
    android:id="@+id/nav_main_graph"
    app:startDestination="@id/loginFragment">

    <fragment
        android:id="@+id/loginFragment"
        android:name="com.bytb.login.LoginFragment"
        android:label="LoginFragment">
        <action
            android:id="@+id/action_goto_home"
            app:destination="@id/homeFragment" />
    </fragment>
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.bytb.login.HomeFragment"
        android:label="HomeFragment" />
</navigation>

注:這里可以通過(guò)拖拽指針來(lái)指定各View之間的關(guān)系

  1. 視圖添加、拖拽演示

  2. 導(dǎo)航到指定視圖,如從LoginFragment轉(zhuǎn)入到HomeFragment

findNavController().navigate(LoginFragmentDirections.actionGotoHome())
  1. 返回到上一級(jí)視圖
findNavController().popBackStack()
  1. 返回到指定的頁(yè)面
    當(dāng)我們?cè)跇I(yè)務(wù)中進(jìn)行如下頁(yè)面導(dǎo)航順序時(shí)FragmentA->DialogFragmentA->DialogFragmentB->DialogFragmentC,當(dāng)前是在DialogFragmentC頁(yè)面,在此頁(yè)面調(diào)用dismiss方法,會(huì)發(fā)現(xiàn)只返回到了DialogFragmentB,而正常情況下,我們是希望返回到彈出彈窗的FragmentA頁(yè)面,那么我們可以使用NavController.kt類(lèi)提供的如下方法達(dá)到目的,源碼如下:
    /**
     * Attempts to pop the controller's back stack back to a specific destination.
     *
     * @param destinationId The topmost destination to retain
     * @param inclusive Whether the given destination should also be popped.
     *
     * @return true if the stack was popped at least once and the user has been navigated to
     * another destination, false otherwise
     */
    @MainThread
    // 參數(shù)一:回退到指定的destinationId
    // 參數(shù)二:表示是否把指定的destinationId也清退出回退棧
    public open fun popBackStack(@IdRes destinationId: Int, inclusive: Boolean): Boolean {
        return popBackStack(destinationId, inclusive, false)
    }

我們可以在DialogFragmentC中調(diào)用以下方法,直接返回到FragmentA頁(yè)面并把之前的DialogFragmentA->DialogFragmentB->DialogFragmentC都清退出回退棧:

findNavController().popBackStack(R.id.FragmentA_destinationId, false)

如果我們想要返回到FragmentA頁(yè)面的上一級(jí),那么只需要修改參數(shù)二為true即可:

findNavController().popBackStack(R.id.FragmentA_destinationId, true)

11.回傳參數(shù)給上一層的Fragment
當(dāng)我們從FragmentA導(dǎo)航到FragmentB進(jìn)行操作時(shí),如果我們?cè)诮Y(jié)束當(dāng)前FragmentB時(shí)想要返回指定的參數(shù)給FragmentA,那么可以通過(guò)SavedStateHandle+LiveData進(jìn)行對(duì)應(yīng)的參數(shù)值傳遞:
FragmentA.kt

findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>("PARAMETER_KEY")?.observe(viewLifecycleOwner) {

}

FragmentB.kt

val navController = findNavController()
navController.previousBackStackEntry?.savedStateHandle?.set("PARAMETER_KEY", "testValue")
navController.popBackStack()

Android 使用 navigation 時(shí)傳遞參數(shù)的兩種方式
SavedStateHandle組件解析
Android Navigation 如何回傳參數(shù)


三、Navigation結(jié)合BottomNavigationView的使用

Activity布局文件中,添加NavigationBottomNavigationView

<?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=".home.MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        app:navGraph="@navigation/nav_main" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation_view"
        android:layout_width="0dp"
        android:layout_height="@dimen/common_measure_56dp"
        android:background="@color/white"
        app:itemIconTint="@drawable/selector_bottom_tab"
        app:itemTextAppearanceActive="@style/bottom_tab_title_active"
        app:itemTextAppearanceInactive="@style/bottom_tab_title_inactive"
        app:itemTextColor="@drawable/selector_bottom_tab"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_nav_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

導(dǎo)航對(duì)應(yīng)的graph文件nav_main.xml內(nèi)容如下:

<?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_main"
    app:startDestination="@id/navigation_home">
    <fragment
        android:id="@+id/navigation_home"
        android:name="com.xxx.HomeFragment"
        android:label="HomeFragment"
        tools:layout="@layout/fragment_home"/>
    <fragment
        android:id="@+id/navigation_mine"
        android:name="com.xxx.MineFragment"
        android:label="MineFragment"
        tools:layout="@layout/fragment_mine"/>
</navigation>

BottomNavigationView對(duì)應(yīng)的菜單文件bottom_nav_menu.xml內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_bn_home_n"
        android:title="@string/tab_home" />

    <item
        android:id="@+id/navigation_mine"
        android:icon="@drawable/ic_bn_mine_n"
        android:title="@string/tab_mine" />

</menu>

注意:graph文件、menu文件中的id要保持一致,即<fragment><item>標(biāo)簽的id必須一致。

ActivityBottomNavigationViewNavigation關(guān)聯(lián)即可:

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment
mViewBinding.bottomNavigationView.setupWithNavController(navHostFragment.navController)

四、使用過(guò)程中的異常情況

1、Navigation action/destination com.xxx:id/action_x cannot be found from the current destination

// 調(diào)用navigate跳轉(zhuǎn)之前先判斷
// https://blog.csdn.net/qq_26658969/article/details/125642527
if (navController.currentDestination?.id == R.id.Afragment) {
    navController.navigate(R.id.action_Afragment_to_Bfragment)
}

2、在http請(qǐng)求回調(diào)中使用Navigation導(dǎo)航報(bào)異常錯(cuò)誤:Method addObserver must be called on the main thread

剛開(kāi)始以為是http請(qǐng)求出了問(wèn)題,各種調(diào)試。。。后來(lái)發(fā)現(xiàn)。。。
又是一個(gè)android BUG NavController在調(diào)用時(shí)Method addObserver must be called on the main thread

參考自

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容