Android:Navigation組件(附加:與BottomNavigationView一起使用)

Navigation組件的出現(xiàn),避免了我們自己去管理Fragment的遷移棧

1、在Gradle中配置Navigation組件依賴

參考:https://developer.android.com/jetpack/androidx/releases/navigation#declaring_dependencies

dependencies {
    ...
    ...
    // For Kotlin use navigation-fragment-ktx
    implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
    // For Kotlin use navigation-ui-ktx
    implementation "android.arch.navigation:navigation-ui-ktx:1.0.0"
}

2、創(chuàng)建導(dǎo)航圖(Navigation Graph)

在項(xiàng)目窗口,右鍵點(diǎn)擊項(xiàng)目目錄,選擇 New > Android Resource File:

image.png

這時(shí)會(huì)彈出添加新資源的對(duì)話框,輸入文件名,例如“nav_graph”,選擇 Resource Type > Navigation:

image.png

點(diǎn)擊OK,導(dǎo)航圖文件就創(chuàng)建好了:

image.png
image.png

3、在activity_main.xml中添加NavHost

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph"/>
</android.support.constraint.ConstraintLayout>
  • android:name
    配置 NavHost 的實(shí)現(xiàn)類,使用默認(rèn)配置的即可。
  • app:defaultNavHost="true"
    確保 NavHostFragment 能攔截系統(tǒng)返回按鈕。
    點(diǎn)擊返回鍵時(shí),頁(yè)面會(huì)按進(jìn)入棧的順序退出棧。
    如果在同一個(gè)布局中配置了多個(gè) NavHost 實(shí)現(xiàn),只能有一個(gè) NavHost 實(shí)現(xiàn)可以把 defaultNavHost 設(shè)置為 true。
  • app:navGraph
    該屬性將 NavHostFragment 與導(dǎo)航圖關(guān)聯(lián)起來(lái)。

添加完之后,在res / navigation / nav_graph.xml 中,HOST 下會(huì)顯示已添加的 NavHost

image.png

4、在導(dǎo)航圖(Navigation Graph)中添加目標(biāo)Fragment(Destinations)

有3中創(chuàng)建 Destinations 的方式:


image.png
  • 方式1:Create new destination
    該方式會(huì)創(chuàng)建一個(gè)新的Fragment,創(chuàng)建完之后會(huì)自動(dòng)把創(chuàng)建的Fragment添加到導(dǎo)航圖中。

    image.png

  • 方式2:Placeholder destinations
    該方式會(huì)使用占位符來(lái)表示未實(shí)現(xiàn)的 Destinations。

    image.png

  • 方式3:直接選中已創(chuàng)建的Fragment
    該方式會(huì)把選中的Fragment作為 Destinations。

Destinations 添加完成后,res / navigation / nav_graph.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/fragment1">
    <fragment
            android:id="@+id/fragment1"
            android:name="com.example.navigationdemo.Fragment1"
            android:label="fragment1"
            tools:layout="@layout/fragment1"/>
</navigation>
  • app:startDestination
    該參數(shù)指定了啟動(dòng) App 后默認(rèn)顯示的 Destinations(即 Fragment)

5、連接目標(biāo) Fragment(Destinations)

點(diǎn)擊其中一個(gè) Fragment 的右側(cè)邊框中間的小圓點(diǎn),然后拖拽到另外一個(gè) Fragment 上,即可完成連接。

image.png

連接完成后,res / navigation / nav_graph.xml 中顯示如下:

image.png
  • android:id 屬性
    我們?cè)诖a里可以通過(guò)該 id 完成兩個(gè) Fragment 之前的遷移。
  • app:destination 屬性
    該屬性指定了當(dāng)前 action 所在的 Fragment 能遷移到的目的地 (Destinations)。
    這里表示使用該 action ,可以從 fragment1 遷移到 fragment2

還可以給 action 配置遷移動(dòng)畫(huà),只需要指定動(dòng)畫(huà)xml即可:

<action
        android:id="@+id/action_fragment1_to_fragment2"
        app:destination="@id/fragment2"
        app:enterAnim="@anim/nav_default_enter_anim"
        app:exitAnim="@anim/nav_default_exit_anim"
        app:popEnterAnim="@anim/nav_default_pop_enter_anim"
        app:popExitAnim="@anim/nav_default_pop_exit_anim"/>

action 還有兩個(gè)參數(shù)需要注意:

  • app:popUpTo
    指定要返回到的目標(biāo)頁(yè)面的 destinationId
  • app:popUpToInclusive
    是否要把當(dāng)前頁(yè)面彈出返回棧

這兩個(gè)參數(shù)與下面介紹的 popBackStack(@IdRes int destinationId, boolean inclusive) 方法類似。

6、代碼中使用 Navigation

6.1、MainActivity 中使用 Navigation

// 獲取 NavController
// R.id.nav_host_fragment 即 activity_main.xml 中配置的 NavHostFragment 的 id
val navController: NavController = Navigation.findNavController(this, R.id.nav_host_fragment)

// 獲取當(dāng)前正在顯示的 Destinations
navController.currentDestination

// 獲取當(dāng)前正在顯示的 Destinations 的 id
navController.currentDestination?.id

// 判斷正在顯示的 Destinations 是不是 Fragment1
// R.id.fragment1 即 res/navigation/nav_graph.xml 中對(duì)應(yīng) fragment 的 id
if (navController.currentDestination?.id == R.id.fragment1) {
  ......
}

6.2、Fragment 中使用 Navigation

// 獲取 NavController
// view 即 Fragment 的 getView() 方法。
// view 擁有 findNavController() 方法,是因?yàn)镹avigation組件依賴?yán)飳?duì) View 進(jìn)行了擴(kuò)展:
// fun View.findNavController(): NavController = Navigation.findNavController(this)
val navController: NavController = view?.findNavController()

// 不帶參數(shù)從 Fragment1 遷移到 Fragment2
// R.id.action_fragment1_to_fragment2 即 res/navigation/nav_graph.xml 中生成的 action 的 id
// 如果 action 配置了動(dòng)畫(huà)屬性,則會(huì)使用動(dòng)畫(huà)遷移
view?.findNavController().navigate(R.id.action_fragment1_to_fragment2)

// 帶參數(shù)從 Fragment1 遷移到 Fragment2
val args = Bundle()
args.putString("title",  "123")
view?.findNavController().navigate(R.id.action_fragment1_to_fragment2, args)

// 返回前一個(gè) Fragment
// 這種方式返回:
// 如果棧中有多個(gè)Fragment,就直接調(diào)用 popBackStack方法進(jìn)行Fragment回退
// 如果當(dāng)前Fragment已經(jīng)是棧中最后一個(gè)Fragment,最終會(huì)調(diào)用Activity的finish()結(jié)束當(dāng)前Activity
view?.findNavController()?.navigateUp()

// 返回前一個(gè) Fragment
// 這種方式返回,默認(rèn)調(diào)用的是 popBackStack(getCurrentDestination().getId(), true)
// 即把當(dāng)前 Fragment 彈出棧,并返回到前一個(gè) Fragment
view?.findNavController()?.popBackStack()

// 根據(jù)指定的 destinationId 進(jìn)行返回
// 第一個(gè)參數(shù) destinationId 是 res/navigation/nav_graph.xml 中對(duì)應(yīng) fragment 的 id
// 第二個(gè)參數(shù) inclusive 表示是否把指定 destinationId 對(duì)應(yīng)的 Fragment 也彈出棧
view?.findNavController()?.popBackStack(@IdRes int destinationId, boolean inclusive)

// 比如當(dāng)前是 Fragment2 ,要返回到 Fragment1
// 如果把 destinationId 設(shè)置為 R.id.fragment2,inclusive 設(shè)為 true,此時(shí) Fragment2 會(huì)彈出棧,并返回到 Fragment1
view?.findNavController()?.popBackStack(R.id.fragment2, true)
// 與下面的效果一樣
view?.findNavController()?.navigateUp()

// 如果把 inclusive 設(shè)為 false,此時(shí)不能成功的返回到 Fragment1,因?yàn)镕ragment2未被彈出棧,一直在棧里,每次返回都是 Fragment2
view?.findNavController()?.popBackStack(R.id.fragment2, false)
// 如果想成功的返回到 Fragment1,可以改成下面這樣,把 destinationId 設(shè)置為 R.id.fragment1
view?.findNavController()?.popBackStack(R.id.fragment1, false)

// 還是上面的例子
// 如果把 destinationId 設(shè)置為 R.id.fragment1,inclusive 設(shè)為 true,此時(shí) Fragment2 會(huì)彈出棧,并返回到 Fragment1
view?.findNavController()?.popBackStack(R.id.fragment1, true)
// 但是,再次從 Fragment1 遷移到 Fragment2 時(shí)就會(huì)報(bào)異常:
view?.findNavController()?.navigate(R.id.action_fragment1_to_fragment2)
// 上面的代碼會(huì)報(bào)異常:java.lang.IllegalArgumentException: navigation destination com.example.navigationdemo:id/action_fragment1_to_fragment2 is unknown to this NavController
// 出現(xiàn)上面異常的原因是因?yàn)?,F(xiàn)ragment2 被彈出棧了,NavController 找不到 action_fragment1_to_fragment2 對(duì)應(yīng)的目的地

6.3、Navigation 與 BottomNavigationView 一起使用

6.3.1、在Gradle中增加相關(guān)依賴

implementation 'com.android.support:design:28.0.0'

6.3.1、創(chuàng)建菜單UI

和導(dǎo)航圖創(chuàng)建類似,選擇 Resource Type > Menu。
創(chuàng)建好之后添加兩個(gè)測(cè)試菜單:

image.png

注意:紅色標(biāo)識(shí)的 id 需要和下面 navigation/nav_graph.xml 中的一致,只有這樣,點(diǎn)擊 item 才會(huì)遷移到對(duì)應(yīng)的 Fragment:

image.png

6.3.2、 修改 activity_main.xml,添加 BottomNavigationView

<android.support.constraint.ConstraintLayout
        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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

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

        <android.support.design.widget.BottomNavigationView
                android:id="@+id/bottom_menu"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                <!-- 菜單項(xiàng)多于3個(gè)時(shí),也顯示Label -->
                <!-- Design 27 使用反射來(lái)修改 mShiftingMode 屬性實(shí)現(xiàn) -->
                <!-- Design 28 中使用下面代碼實(shí)現(xiàn) -->
                app:labelVisibilityMode="labeled"
                <!-- 如果要禁用水波紋效果,請(qǐng)修改app:itemBackground屬性 -->
                app:menu="@menu/menu"/>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

6.3.3、 修改MainActivity

把BottomNavigationView與Navigation進(jìn)行綁定,綁定后即可測(cè)試使用了。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 獲取NavController
        val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
        // 把BottomNavigationView與NavController綁定。
        // 綁定后,當(dāng)菜單項(xiàng)被選中時(shí),會(huì)調(diào)用NavigationUI.onNavDestinationSelected(menuItem, navController)方法
        // 這里有個(gè)注意事項(xiàng),上面創(chuàng)建菜單時(shí)有說(shuō)明,下面手動(dòng)綁定的注釋也有說(shuō)明
        // BottomNavigationView擁有setupWithNavController()方法,是因?yàn)镹avigation組件依賴?yán)飳?duì)BottomNavigationView進(jìn)行了擴(kuò)展:
        // fun BottomNavigationView.setupWithNavController(navController: NavController) {
        //    NavigationUI.setupWithNavController(this, navController)
        // }
        bottom_menu.setupWithNavController(navController)

        // 如果不用上面的方式,可以用下面的方式自己手動(dòng)把菜單選中事件與Navigation的導(dǎo)航事件綁定
        bottom_menu.setOnNavigationItemSelectedListener { menuItem ->
            // 導(dǎo)航到與menuItem菜單項(xiàng)關(guān)聯(lián)的NavDestination,即與menu.xml中item的id相同的destinationId
            // destinationId即navigation/nav_graph.xml中fragment的id
            NavigationUI.onNavDestinationSelected(menuItem, navController)
        }
    }
}
最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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