安卓navigation系列——入門篇

做者

​ 你們好,我叫小琪;android

​ 本人16年畢業於中南林業科技大學軟件工程專業,畢業後在教育行業作安卓開發,後來於19年10月加入37手遊安卓團隊;shell

​ 目前主要負責國內發行安卓相關開發,同時兼顧內部幾款App開發。 ​安全

目錄

前言

在平常開發中,愈來愈多的會使用到一個activity嵌套多個fragment的場景,典型的例子就是app的首頁,通常都會由一個activity+多個子tab組成,那對於Fragment的顯示、隱藏等咱們一般都是經過FragmentManager進行管理,但這種方式很容易形成代碼臃腫,難以維護。ide

而經過Jetpack的導航組件——Navigation,就能夠很方便的管理各fragment之間的切換,讓開發變得更簡單。oop

組成三要素

Navigation graph

一個包含全部導航相關信息的 XML 資源佈局

NavHostFragment

一種特殊的Fragment,用於承載導航內容的容器post

NavController

管理應用導航的對象,實現Fragment之間的跳轉等操做

基本使用

引入依賴

implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
複製代碼

建立導航視圖

首先確保AndroidStudio爲3.3以上

1.右鍵res,點擊New -> Android Resource Directory

2.在出現的面板第二行Resource type 下拉列表中選擇 Navigation,而後點擊 OK

3.res目錄下會多出一個navigation的資源目錄,右鍵該目錄,點擊New -> Navigation Resource File,輸入須要新建的資源文件名,這裏命名nav_graph,點擊ok,一個nav_graph.xml就建立好了。

配置graph

新建好的nav_graph.xml切換到design模式下,點擊2處的加號,選擇Create new destination,便可快速建立新的Fragment,這裏分別新建了FragmentA、FragmentB、FragmentC三個fragment

建好後,可經過手動配置頁面之間的跳轉關係,點擊某個頁面,右邊會出現一個小圓點,拖曳小圓點指向跳轉的頁面,這裏設置跳轉的關係爲FragmentA -> FragmentB -> FragmentC。

切換到Code欄,能夠看到生成了以下代碼

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

    <fragment android:id="@+id/fragmentA" android:name="com.example.testnavigation.FragmentA" android:label="fragment_a" tools:layout="@layout/fragment_a" >
        <action android:id="@+id/action_fragmentA_to_fragmentB2" app:destination="@id/fragmentB" />
    </fragment>
    <fragment android:id="@+id/fragmentB" android:name="com.example.testnavigation.FragmentB" android:label="fragment_b" tools:layout="@layout/fragment_b" >
        <action android:id="@+id/action_fragmentB_to_fragmentC2" app:destination="@id/fragmentC" />
    </fragment>
    <fragment android:id="@+id/fragmentC" android:name="com.example.testnavigation.FragmentC" android:label="fragment_c" tools:layout="@layout/fragment_c" />
</navigation>
複製代碼
  • navigation是根標籤,經過startDestination配置默認啓動的第一個頁面,這裏配置的是FragmentA
  • fragment標籤表明一個fragment,其實這裏不只能夠配置fragment,也能夠配置activity,甚至還能夠自定義(暫不討論,後續會講到)
  • action標籤訂義了頁面跳轉的行爲,至關於上圖中的每條線,destination定義跳轉的目標頁,還能夠定義跳轉時的動畫等等

添加NavHostFragment

在MainActivity的佈局文件中配置NavHostFragment

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

    <fragment android:id="@+id/fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼
  • android:name指定NavHostFragment
  • app:navGraph指定導航視圖,即建好的nav_graph.xml
  • app:defaultNavHost=true 意思是能夠攔截系統的返回鍵,能夠理解爲默認給fragment實現了返回鍵的功能,這樣在fragment的跳轉過程當中,當咱們按返回鍵時,就可使得fragment跟activity同樣能夠回到上一個頁面了

如今咱們運行程序,就能夠正常跑起來了,而且看到了FragmentA展現的頁面,這是由於MainActivity的佈局文件中配置了NavHostFragment,而且給NavHostFragment指定了導航視圖,而導航視圖中經過startDestination指定了默認展現FragmentA。

經過NavController 管理fragment之間的跳轉

上面說到三個fragment之間的跳轉關係是FragmentA -> FragmentB -> FragmentC,而且已經能夠展現了FragmentA,那怎麼跳轉到FragmentB呢,這就須要用到NavController 了

打開FragmentA類,給佈局中的TextView定義一個點擊事件

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    tv.setOnClickListener {
        val navController = Navigation.findNavController(it)
        navController.navigate(R.id.action_fragmentA_to_fragmentB2)
    }
}
複製代碼

若是發現不能自動導入佈局文件,大機率是要給app.build添加插件‘kotlin-android-extensions’

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
複製代碼

AndroidStudio4.1之後改爲了

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}
複製代碼

能夠看到,經過navController管理fragment的跳轉很是簡單,首先獲得navController對象,而後調用它的navigate方法,傳入前面nav_graph中定義的action的id便可。

按一樣的方法給FragmentB中的TextView也設置一個點擊事件,使得點擊時跳轉到FragmentC

運行程序,FragmentA -> FragmentB -> FragmentC,此時按返回鍵,也是一個一個頁面返回,若是把前面的app:defaultNavHost設置爲false,按返回鍵後會發現直接返回到桌面了,如今能體會到app:defaultNavHost這個屬性的含義了吧。

更多用法

在編輯nav_graph的時候,action屬性除了設置目標頁外,還能夠設置動畫、頁面間參數傳遞、fragment回退棧管理等

動畫

  • enterAnim: 跳轉時的目標頁面動畫

  • exitAnim: 跳轉時的原頁面動畫

  • popEnterAnim: 回退時的目標頁面動畫

  • popExitAnim:回退時的原頁面動畫

配置動畫後會發現action多了四個動畫相關的屬性

<fragment android:id="@+id/fragmentA" android:name="com.example.testnavigation.FragmentA" android:label="fragment_a" tools:layout="@layout/fragment_a" >
        <action android:id="@+id/action_fragmentA_to_fragmentB2" app:destination="@id/fragmentB" app:enterAnim="@anim/enter_in" app:exitAnim="@anim/enter_out" app:popEnterAnim="@anim/exit_in" app:popExitAnim="@anim/exit_out" />
</fragment>
複製代碼

參數

上面的例子中介紹fragment之間的跳轉,固然也能夠支持參數傳遞。

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    tv.setOnClickListener {
        val navController = Navigation.findNavController(it)
        val bundle = Bundle()
        bundle.putString("key", "test")
        navController.navigate(R.id.action_fragmentA_to_fragmentB2, bundle)
    }
}
複製代碼

參數傳遞推薦使用谷歌官方的safeArgs,safe args與傳統傳參方式相比,好處在於安全的參數類型,而且經過谷歌官方的支持,能很方便的進行參數傳值。

在項目的根build.gradle下添加插件

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.1"
複製代碼
buildscript {
    ext.kotlin_version = "1.3.72"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.1"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}
複製代碼

而後在app的build.gradle中引用 'androidx.navigation.safeargs.kotlin'

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs.kotlin'
複製代碼

AS4.1之後:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'androidx.navigation.safeargs.kotlin'
}
複製代碼

添加完插件後,回到nav_graph,切到design模式,給目標頁面添加須要接收的參數,這裏須要在FragmentA跳轉到FragmentB時傳參數,因此給FragmentB設置參數,點擊FragmentB,點擊右側面板的Arguments右側的+,輸入參數的key值,指定參數類型和默認值,便可快速添加參數

添加完後,rebuild一下工程,safeArgs會自動生成一些代碼,在/build/generated/source/navigation-args目錄下能夠看到

safeArgs會根據nav_graph中的fragment標籤生成對應的類,action標籤會以「類名+Directions」命名,argument標籤會以「類名+Args」命名。

使用safeArgs後,傳遞參數是這樣的

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        tv.setOnClickListener {
            val navController = Navigation.findNavController(it)
            //經過safeArgs傳遞參數
            val navDestination = FragmentADirections.actionFragmentAToFragmentB2("test")
            navController.navigate(navDestination)
            
            // 普通方式傳遞參數
		   // val bundle = Bundle()
            // bundle.putString("key", "test")
            // navController.navigate(R.id.action_fragmentA_to_fragmentB2, bundle)
        }
    }
複製代碼

接收參數是這樣的

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        arguments?.let {
            val value = FragmentBArgs.fromBundle(it).key
            .......
        }
        .......
    }
複製代碼

棧管理

點擊destination,右側面板中還能夠看到popUpTo、popUpToInclusive、launchSingleTop

  • launchSingleTop:若是棧中已經包含了指定要跳轉的界面,那麼只會保留一個,不指定則棧中會出現兩個界面相同的Fragment數據,能夠理解爲相似activity的singleTop,即棧頂複用模式,但又有點不同,好比FragmentA@1 -> FragmentA@2,FragmentA@1會被銷燬,但若是是FragmentA@01>FragmentB@02>FragmentA@03,FragmentA@1不會被銷燬。
  • popUpTo(tag):表示跳轉到某個tag,並將tag之上的元素出棧。
  • popUpToInclusive:爲true表示會彈出tag,false則不會

例子:FragmentA -> FragmentB -> FragmentC -> FragmentA

設置FragmentC -> FragmentA 的action爲popUpTo=FragmentA ,popUpToInclusive=false,那麼棧內元素變化爲

最後會發現須要按兩次返回鍵纔會回退到桌面

設置popUpToInclusive=true時,棧內元素變化爲

此時只須要按一次返回鍵就回退到桌面了,從中能夠體會到popUpTo和popUpToInclusive的含義了吧。

deeplink

深度連接,就是能夠直接跳轉到某個頁面。navigation建立深度連接能夠經過顯示和隱式兩種方式

按以前的方式新建一個須要經過深度連接打開的目標頁面FragmentDeepLink,

接下來爲它建立一個deeplink

nav_graph.xml相應的在生成了以下代碼

<fragment android:id="@+id/fragmentDeepLink" android:name="com.example.testnavigation.FragmentDeepLink" android:label="fragment_deep_link" tools:layout="@layout/fragment_deep_link">
    <argument android:name="key" android:defaultValue="測試" app:argType="string" />
    <deepLink android:id="@+id/deepLink" app:uri="www.deeplink.com/{id}" />
</fragment>
複製代碼
  • 顯示深度連接

    顯示深層連接使用PendingIntent來導航到特定頁面,好比點擊通知欄,快速打開目標頁面。

    tv_deeplink.setOnClickListener {
        //顯示深度連接
        val notificationManager = NotificationManagerCompat.from(requireContext())
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel =
                NotificationChannel(channelId, channelName, importance)
            channel.description = "deeplink"
            notificationManager.createNotificationChannel(channel)
        }
    
        val navController = Navigation.findNavController(it)
        val deepLinkBuilder = navController.createDeepLink()
        val bundle = Bundle()
        bundle.putString("key", "deeplink")
        val pendingIntent = deepLinkBuilder
             //傳入graph資源文件
            .setGraph(R.navigation.nav_graph)
            //傳入參數
            .setArguments(bundle)
            //傳入須要經過深度連接打開的目標頁面
            .setDestination(R.id.fragmentDeepLink)
            .createPendingIntent()
        val builder = NotificationCompat.Builder(requireContext(), channelId)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("測試deepLink")
            .setContentText("哈哈哈")
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        notificationManager.notify(1, builder.build())
    }
    複製代碼
  • 隱式深度連接

    隱式連接是當用戶點擊某個連接的時候,經過URI跳轉到某個頁面,剛剛已經爲nav_graph.xml中的FragmentDeepLink添加了

    <deepLink app:uri="www.deeplink.com/{id}" />
    複製代碼

    該uri沒有聲明是http仍是https,那麼這兩個都能匹配。大括號內的是傳遞的參數。

    AndroidManifest.xml中給FragmentDeepLink所屬的activity添加一個<nav-graph>屬性,這爲MainActivity

    <activity android:name=".MainActivity">
        ......
        <nav-graph android:value="@navigation/nav_graph"/>
    </activity>
    複製代碼

    在build後,經過目錄app - > build - > outputs - > apk - > debug - > app-debug.apk查看AndroidManifest.xml中的MainActivity節點下會多出以下代碼

<intent-filter>

    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />

    <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="http" />

    <data android:scheme="https" />

    <data android:host="www.deeplink.com" />

    <data android:pathPrefix="/" />
</intent-filter>
複製代碼

Navigation 組件會將 <nav-graph> 元素替換爲生成的 <intent-filter> 元素來匹配深層連接。

咱們能夠經過adb來測試隱式深層連接的效果,打開命令行輸入

adb shell am start -a android.intent.action.VIEW -d "http://www.deeplink.com/1"
複製代碼

在系統彈出的窗口中,選擇本身的應用打開,就能跳轉到目標頁面了。

總結

本篇是navigation的入門篇,主要介紹了navigation的基本使用,下篇將從源碼角度,剖析navigation是如何作到頁面之間跳轉的。

結束語

過程當中有問題或者須要交流的同窗,能夠掃描二維碼加好友,而後進羣進行問題和技術的交流等;

企業微信截圖_5d79a123-2e31-42cc-b03f-9312b8b99df3.png

相關文章
相關標籤/搜索