Jetpack之Navigation

navigation是負(fù)責(zé)fragment之間切換的處理工具

有三個核心點:

  • Navigation Graph (New XML Resource): 控制中心鲜戒,包含所有頁面及頁面間的關(guān)系
  • NavHostFragment (Layout XML View):特殊的Fragment隆圆,是其他Fragment的“容器”,Navigation Graph中所有Fragmnet正是通過這個特殊的Fragment進(jìn)行展示的
  • NavController (Kotlin / Java Object): 負(fù)責(zé)完成Navigation中具體的頁面切換工作

三者之間的關(guān)系:當(dāng)需要切換頁面時俭识,使用NavController對象,告訴它想要去的Navigation Graph中的哪個Fragment洞渔,NavController對象會將目的地的Fragment展示在NavHostFragment中套媚。

一、基本使用

1. 添加依賴

dependencies {
    
    ......
    def nav_version = "2.3.5"

    // Kotlin
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

2. 創(chuàng)建Navigation Graph

res文件夾 -> New -> Android Resource File


Create Navigation Graph

3. 添加NavHostFragmnet

在activity_main.xml文件中添加fragment作為NavHostFragment磁椒,其中有三點需要注意:

  • name必須是"androidx.navigation.fragment.NavHostFragment"
  • defaultNavHost設(shè)置為"true"
  • navGraph設(shè)置為"@navigation/nav_graph"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".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" />

</RelativeLayout>

4. 創(chuàng)建Destination

在nav_graph.xml頁面里堤瘤,依次點擊加號、 Create new destination創(chuàng)建一個Destination浆熔,首次在此創(chuàng)建一個MainFragment作為StartDestination

<?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/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.mynavigationdemo.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main" />
</navigation>

5. 頁面切換

在nav_graph.xml頁面里本辐,就像創(chuàng)建MainFragment一樣創(chuàng)建一個SecondFragment

<?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/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.mynavigationdemo.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main" />
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.mynavigationdemo.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
</navigation>

5.1 創(chuàng)建Action

在nav_graph.xml頁面的Design面板里,單擊MainFragment,然后選中右側(cè)的圓圈熱點并拖拽指向右側(cè)的SecondFragmnt


選中熱點并拖拽

Action創(chuàng)建完成
<?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/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.mynavigationdemo.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main">
        <action
            android:id="@+id/action_mainFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.mynavigationdemo.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
</navigation>

5.2 使用NavController導(dǎo)航到目的地

在fragment_main.xml中添加一個按鈕慎皱,然后在MainFragment文件中添加該按鈕點擊事件的監(jiān)聽器环葵,當(dāng)點擊該按鈕時通過NavController完成頁面跳轉(zhuǎn)

class MainFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.findViewById<Button>(R.id.btnToSecondFragment).setOnClickListener {
            findNavController().navigate(R.id.action_mainFragment_to_secondFragment)
        }
    }
}

導(dǎo)航到目的地是使用 NavController 完成的,它是一個在 NavHost 中管理應(yīng)用導(dǎo)航的對象宝冕。每個 NavHost 均有自己的相應(yīng)NavController张遭,NavController 提供了幾種導(dǎo)航到目的地的不同方式,如需從 Fragment地梨、Activity 或View中獲取NavController菊卷,請使用以下某種方法:

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

5.3 添加切換動畫

在nav_graph.xml頁面的Design面板里,點擊MainFragment指向SecondFragment的箭頭(即頁面跳轉(zhuǎn)Action)宝剖,然后在Attributes面板的Animations部分中洁闰,點擊要添加的動畫旁邊的下拉箭頭,選擇需要設(shè)置的動畫


添加動畫
<?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/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.mynavigationdemo.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main">

        <action
            android:id="@+id/action_mainFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@android:anim/fade_in"
            app:exitAnim="@android:anim/fade_out"
            app:popEnterAnim="@android:anim/fade_in"
            app:popExitAnim="@android:anim/fade_out" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.mynavigationdemo.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
</navigation>

6. 傳遞參數(shù)

6.1 基本類型參數(shù)

6.1.1 添加參數(shù)

在nav_graph.xml頁面的Design面板里万细,點擊接收參數(shù)的目的地Fragment(此處為secondFragment), 然后在Attributes面板的Arguments列點擊右側(cè)添加按鈕(+)可添加參數(shù)

添加參數(shù)
<?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/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.mynavigationdemo.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main">

        <action
            android:id="@+id/action_mainFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@android:anim/fade_in"
            app:exitAnim="@android:anim/fade_out"
            app:popEnterAnim="@android:anim/fade_in"
            app:popExitAnim="@android:anim/fade_out" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.mynavigationdemo.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" >
        <argument
            android:name="name"
            app:argType="string"
            android:defaultValue='"HSG"' />
    </fragment>
</navigation>
6.1.2 接收參數(shù)
class SecondFragment : Fragment() {
    val TAG = "SecondFragment"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val name = arguments?.getString("name")
        Log.d(TAG, "onCreate() called with: name = $name")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false)
    }
}

logcat 輸出:D/SecondFragment: onCreate() called with: name = "HSG"

6.1.3 傳遞參數(shù)
class MainFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.findViewById<Button>(R.id.btnToSecondFragment).setOnClickListener {
            val bundle = Bundle()
            bundle.putString("name", "Hello World")
            findNavController().navigate(R.id.action_mainFragment_to_secondFragment, bundle)
        }
    }
}

logcat 輸出:D/SecondFragment: onCreate() called with: name = Hello Word

6.2 使用safe args傳遞參數(shù)

該插件可以生成簡單的 object和builder類扑眉,以便以類型安全的方式瀏覽和訪問任何關(guān)聯(lián)的參數(shù)。我們強烈建使用Safe Args進(jìn)行數(shù)據(jù)傳遞赖钞,因為它可以確保類型安全腰素。

6.2.1 添加依賴

在project的build.gradle文件中添加safe args插件:

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.5"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

在app的build.gradle文件中添加:

(1)如需生成適用于 Java 模塊或 Java 和 Kotlin 混合模塊的 Java 語言代碼,請?zhí)砑樱?/p>

apply plugin: "androidx.navigation.safeargs"

(2)如需生成適用于僅 Kotlin 模塊的 Kotlin 語言代碼雪营,請?zhí)砑樱?/p>

apply plugin: "androidx.navigation.safeargs.kotlin"

根據(jù)遷移到 AndroidX 文檔弓千,gradle.properties 文件中必須具有 android.useAndroidX=true

啟用 Safe Args 后,生成的代碼會為每個Action(包含發(fā)送方和接收方)提供類型安全的類和方法:

  • 為Action的每一個發(fā)送方創(chuàng)建一個類献起。該類的名稱是在發(fā)送方的名稱后面加上“Directions”洋访。例如,如果發(fā)送方是名為MainFragment的Fragment,則生成的類的名稱為 MainFragmentDirections,該類會為發(fā)送方中定義的每個Action提供一個方法迈套。
  • 對于用于傳遞參數(shù)的每個Action,都會創(chuàng)建一個 inner 類诡宗,該類的名稱根據(jù)Action的名稱確定。例如,如果Action名稱為 action_mainFragment_to_secondFragment,則類名稱為 ActionMainFragmentToSecondFragment善镰,可以此關(guān)聯(lián)的 action 類來設(shè)置參數(shù)值妹萨。
  • 為接收目方創(chuàng)建一個類年枕。該類的名稱是在接收方的名稱后面加上“Args”。例如乎完,如果接收方的 Fragment 的名稱為 SecondFragment熏兄,則生成的類的名稱為 SecondFragmentArgs。可以使用該類的 fromBundle() 方法獲取傳遞的參數(shù)摩桶。
6.2.2 傳遞參數(shù)
class MainFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.findViewById<Button>(R.id.btnToSecondFragment).setOnClickListener {
//            val bundle = Bundle()
//            bundle.putString("name", "Hello World")
//            findNavController().navigate(R.id.action_mainFragment_to_secondFragment, bundle)
            val direction = MainFragmentDirections.actionMainFragmentToSecondFragment("Hello Android")
            findNavController().navigate(direction)
        }
    }
}
6.2.3 接收參數(shù)
class SecondFragment : Fragment() {
    val TAG = "SecondFragment"
    private val args by navArgs<SecondFragmentArgs>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        val name = arguments?.getString("name")
        Log.d(TAG, "onCreate() called with: name = ${args.name}")
    }
}

logcat 輸出:D/SecondFragment: onCreate() called with: name = Hello Android

7. 配置ActionBar

通過Navigation完成頁面跳轉(zhuǎn)已基本完成桥状,但和Activity跳轉(zhuǎn)相比仍有不足之處——當(dāng)前的ActionBar標(biāo)題不會跟隨Navigation頁面變化,而且也沒有用于返回的小圖標(biāo)硝清。因此辅斟,本節(jié)就來解決該問題,Navigation已提供了便捷的解決方案芦拿,只需配置NavController對ActionBar進(jìn)行控制士飒,即可解決該問題。

在MainActivity中蔗崎,通過四步即可完成配置:
第1步酵幕,在Activity上獲取NavController

navController = findNavController(R.id.nav_host_fragment)

第2步,配置AppBarConfiguration缓苛,以便NavController接管ActionBar后能正確進(jìn)行控制

appbarConfiguration = AppBarConfiguration.Builder(navController.graph).build()

第3步芳撒,配置NavController對ActionBar進(jìn)行控制

NavigationUI.setupActionBarWithNavController(this, navController, appbarConfiguration)

第4步,讓NavController接管返回事件

override fun onSupportNavigateUp(): Boolean {
        return NavigationUI.navigateUp(
            navController,
            appbarConfiguration
        ) or super.onSupportNavigateUp()
    }

完整代碼:

class MainActivity : AppCompatActivity() {
    private lateinit var appbarConfiguration: AppBarConfiguration
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        navController = findNavController(R.id.nav_host_fragment)
        appbarConfiguration = AppBarConfiguration.Builder(navController.graph).build()
        NavigationUI.setupActionBarWithNavController(this, navController, appbarConfiguration)
    }

    override fun onSupportNavigateUp(): Boolean {
        return NavigationUI.navigateUp(
            navController,
            appbarConfiguration
        ) or super.onSupportNavigateUp()
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末未桥,一起剝皮案震驚了整個濱河市笔刹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冬耿,老刑警劉巖徘熔,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淆党,居然都是意外死亡酷师,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門染乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來山孔,“玉大人,你說我怎么就攤上這事荷憋√ǖ撸” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵勒庄,是天一觀的道長串前。 經(jīng)常有香客問我,道長实蔽,這世上最難降的妖魔是什么荡碾? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮局装,結(jié)果婚禮上坛吁,老公的妹妹穿的比我還像新娘劳殖。我一直安慰自己,他們只是感情好拨脉,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布哆姻。 她就那樣靜靜地躺著,像睡著了一般玫膀。 火紅的嫁衣襯著肌膚如雪矛缨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天帖旨,我揣著相機(jī)與錄音劳景,去河邊找鬼。 笑死碉就,一個胖子當(dāng)著我的面吹牛盟广,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓮钥,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼筋量,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碉熄?” 一聲冷哼從身側(cè)響起桨武,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锈津,沒想到半個月后呀酸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡琼梆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年性誉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茎杂。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡错览,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出煌往,到底是詐尸還是另有隱情倾哺,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布刽脖,位于F島的核電站羞海,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏曲管。R本人自食惡果不足惜却邓,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翘地。 院中可真熱鬧申尤,春花似錦癌幕、人聲如沸衙耕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橙喘。三九已至时鸵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厅瞎,已是汗流浹背饰潜。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留和簸,地道東北人彭雾。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像锁保,于是被迫代替她去往敵國和親薯酝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容