你們好,我叫小琪;android
本人16年畢業於中南林業科技大學軟件工程專業,畢業後在教育行業作安卓開發,後來於19年10月加入37手遊安卓團隊;shell
目前主要負責國內發行安卓相關開發,同時兼顧內部幾款App開發。 安全
navigation——入門篇(本章講解)微信
navigation——進階篇markdown
navigation——實戰篇 (敬請期待...)app
在平常開發中,愈來愈多的會使用到一個activity嵌套多個fragment的場景,典型的例子就是app的首頁,通常都會由一個activity+多個子tab組成,那對於Fragment的顯示、隱藏等咱們一般都是經過FragmentManager進行管理,但這種方式很容易形成代碼臃腫,難以維護。ide
而經過Jetpack的導航組件——Navigation,就能夠很方便的管理各fragment之間的切換,讓開發變得更簡單。oop
一個包含全部導航相關信息的 XML 資源佈局
一種特殊的Fragment,用於承載導航內容的容器post
管理應用導航的對象,實現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就建立好了。
新建好的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>
複製代碼
在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>
複製代碼
如今咱們運行程序,就能夠正常跑起來了,而且看到了FragmentA展現的頁面,這是由於MainActivity的佈局文件中配置了NavHostFragment,而且給NavHostFragment指定了導航視圖,而導航視圖中經過startDestination指定了默認展現FragmentA。
上面說到三個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
例子:FragmentA -> FragmentB -> FragmentC -> FragmentA
設置FragmentC -> FragmentA 的action爲popUpTo=FragmentA ,popUpToInclusive=false,那麼棧內元素變化爲
最後會發現須要按兩次返回鍵纔會回退到桌面
設置popUpToInclusive=true時,棧內元素變化爲
此時只須要按一次返回鍵就回退到桌面了,從中能夠體會到popUpTo和popUpToInclusive的含義了吧。
深度連接,就是能夠直接跳轉到某個頁面。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是如何作到頁面之間跳轉的。
過程當中有問題或者須要交流的同窗,能夠掃描二維碼加好友,而後進羣進行問題和技術的交流等;