JetPack Navigation 介紹
Jetpack Navigation 是啥
Jetpack Navigation 是 Jetpack 里面的一個依賴庫,用來對Fragment 頁面的導航和管理的。
使用
1 引用
// Kotlin
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
2 創(chuàng)建Navigation 導航文件
- 1 在 res 下面創(chuàng)建navigation 目錄
- 2 右鍵navigation 創(chuàng)建 xml 文件 nav_graph(名字隨便起)
-
3 如圖所示點擊創(chuàng)建 一個 destination
按照提示添加Fragment博秫。
-
4 依次同樣的方法添加多個fragment :HomeFragment脚祟,LoginFragmrnt,UserFragment,RegisterFragment浙滤,ContentFragment茶行,如下圖是自動生成躯概。
-
5 給這些fragment 增加關系。
增加下面的線之后左邊就會自動增加Action的標簽畔师。線1 增加的時候左邊對應就會增加一個action娶靡,
線2 畫出來的時候左邊就對應增加一個2的action。
右邊的線都是拖拽就可以的看锉,現(xiàn)在左邊的代碼還都是自動生成的姿锭。
3把navigation 圖展示出來
activity 的xml 把navigation 加入進來,下面是mainactivity 的布局。
<?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">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
android:layout_width="0dp"
android:layout_height="0dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
FragmentContainerView 是用來展示navigation 的布局伯铣。
注意 name字段 呻此、 navGraph 字段和defaultNavHost 字段。
name 字段是加載的fragment 里面用來處理 destination 的邏輯跳轉的容器腔寡。
navGraph 字段是用來 指示navigation焚鲜。
defaultNavHost 字段用來表示 返回鍵單獨處理不,默認單獨處理走fragment的棧的地方放前。
這樣MainActivity 就展示的是nav_graph 的startDestination 也就是homeFragment忿磅。
4 navigation 之間的跳轉
下面這一行就是最簡單的跳轉方法
view.findViewById<Button>(R.id.btn_login).setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_loginFragment)
}
findNavController() 是Fragment 的拓展方法。
package androidx.navigation.fragment
public fun Fragment.findNavController(): NavController =
NavHostFragment.findNavController(this)
所有的對fragment的管理都是通過NavController 管理的凭语。
返回: findNavController().popBackStack() 或者 findNavController().navigateUp()
// findNavController().popBackStack() 可以越級back
// findNavController().navigateUp() 在一定的條件下可以finish activity
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.btn_back).setOnClickListener {
// findNavController().popBackStack()
findNavController().navigateUp()
}
}
5 NavController
NavController 是核心類
navigate 方法
1 按照 action 跳轉
這種方法是根據(jù)actionId 進行跳轉官方推薦葱她,網(wǎng)上文章大部分都是這樣的使用。
如圖4 里面fragment的里面有 action 叽粹,根據(jù)action Id 進行跳轉览效。
findNavController().navigate(R.id.action_homeFragment_to_loginFragment)却舀。
2 按照deepLink 跳轉
使用較少,網(wǎng)上介紹的也比較少锤灿,但是個人推薦這種方法挽拔。
- 1 像上面圖三 一樣添加fragment
- 2 不給fragment 添加關系給他們手動添加 deepLink
<fragment
android:id="@+id/DFragment1"
android:name="com.eswincomputing.navigationdemo.deeplink.DFragment1"
android:label="fragment_d1"
tools:layout="@layout/fragment_d1" >
<deepLink
android:id="@+id/deep_link_d1"
app:uri="android-app://androidx.navigation/d1"/>
</fragment>
注意 uri 里面 android-app://androidx.navigation/ 這塊的字符串要寫固定。
用法
findNavController().navigate("d1")
findNavController().navigate("d1")
....
@JvmOverloads
public fun navigate(
route: String,
navOptions: NavOptions? = null,
navigatorExtras: Navigator.Extras? = null
) {
navigate(
NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(), navOptions,
navigatorExtras
)
}
···
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun createRoute(route: String?): String =
if (route != null) "android-app://androidx.navigation/$route" else ""
3 navigate 方法參數(shù)
- 參數(shù) 1 action 和 deepLink 只有第一個參數(shù)不對其他的參數(shù)都是一樣的但校,
以action 為例
val navOptions = NavOptions.Builder()
.setLaunchSingleTop(true) // singleTop
.setPopUpTo(R.id.homeFragment, // id 上的fragment 彈出(默認不包含這個id)
inclusive = true, // 是否包含當前的id
saveState = true // 是否保存退出站的狀態(tài)
)
.setRestoreState(true) // 獲取已經(jīng)保存的狀態(tài)
.build()
//
findNavController().navigate(
R.id.action_homeFragment_to_loginFragment, // 參數(shù)1 actionId
Bundle().apply {
putString(ARG_PARAM1,"param_from_home")
putString(ARG_PARAM2,"param_to_login")
}, //參數(shù)2 bundle
navOptions, //參數(shù)3
FragmentNavigatorExtras() // 參數(shù)4
)
參數(shù) 2 是 bundle fragment setargument 里面的參數(shù) 給fragment 傳遞參數(shù)的
參數(shù)3 navOptions 主要設置棧的操作(更多的選擇設置)
setLaunchSingleTop 設置 singleTop
setPopUpTo(id螃诅,inclusive ,saveState )
參數(shù) // id 的fragment 彈出(默認不包含這個id)
參數(shù) inclusive // 是否包含當前的id的fragment
參數(shù) saveState // 是否保存退出站的狀態(tài)
setRestoreState 獲取進入的framgnet 的state(之前保存的)
1.1 findNavController().navigate(R.id.action_homeFragment_to_loginFragment)
傳一個resourceId
添加一個 bundle參數(shù)傳遞
- 2 返回
popBackStack() 正常一個一個退出
popBackStack(
@IdRes destinationId: Int,
inclusive: Boolean,
saveState: Boolean
)
destinationId 目的地fragmentid 退出這個id 上面的fragment
inclusive 是否包含這個id 目的地id
saveState 推出的fragment 是否保存state
默認加載fragment 邏輯
FragmentContainerView constructer 方法 --》 NavHostFragment
oncreate 設置屬性 和 graphID
FragmentContainerView onInflate --》 NavHostFragment 的 onInflate 獲取graphID 状囱。
NavHostFragment oncreate --》
navController!!.setGraph(graphId) --》 navInflater.inflate(graphResId) -》
NavHostController--onGraphCreated -- 》NavHostController----navigate --》 FragmentNavigator —— onLaunchSingleTop术裸,navigateInternal ——》
關鍵類
NavController (NavHostController)
navigate 和pop 跳轉退出FragmentContainerView
activity 里面包含navigationFragment 的View
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_deeplink"
android:layout_width="0dp"
android:layout_height="0dp"/>
注意name
android:name="androidx.navigation.fragment.NavHostFragment"
FragmentContainerView {
constructer(){
···
val containerFragment: Fragment =
fm.fragmentFactory.instantiate(context.classLoader, name)
containerFragment.onInflate(context, attrs, null)
fm.beginTransaction()
.setReorderingAllowed(true)
.add(this, containerFragment, tag)
.commitNowAllowingStateLoss()
}
···
}
根據(jù)name 設置 NavHostFragment View
- 2 NavHostFragment
注意 containerFragment.onInflate(context, attrs, null)
把 FragmentContainerView 的參數(shù) attrs 都傳遞給 NavHostFragment 了
public override fun onInflate(
context: Context,
attrs: AttributeSet,
savedInstanceState: Bundle?
) {
super.onInflate(context, attrs, savedInstanceState)
context.obtainStyledAttributes(
attrs,
androidx.navigation.R.styleable.NavHost
).use { navHost ->
val graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0
)
if (graphId != 0) {
this.graphId = graphId
}
}
context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment).use { array ->
val defaultHost = array.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false)
if (defaultHost) {
defaultNavHost = true
}
}
}
- 4 NavInflater 解析 graph
@SuppressLint("ResourceType")
public fun inflate(@NavigationRes graphResId: Int): NavGraph {
val res = context.resources
val parser = res.getXml(graphResId)
val attrs = Xml.asAttributeSet(parser)
return try {
var type: Int
while (parser.next().also { type = it } != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT
) { /* Empty loop */
}
if (type != XmlPullParser.START_TAG) {
throw XmlPullParserException("No start tag found")
}
val rootElement = parser.name
val destination = inflate(res, parser, attrs, graphResId)
require(destination is NavGraph) {
"Root element <$rootElement> did not inflate into a NavGraph"
}
destination
} catch (e: Exception) {
throw RuntimeException(
"Exception inflating ${res.getResourceName(graphResId)} line ${parser.lineNumber}",
e
)
} finally {
parser.close()
}
}
@Throws(XmlPullParserException::class, IOException::class)
private fun inflate(
res: Resources,
parser: XmlResourceParser,
attrs: AttributeSet,
graphResId: Int
): NavDestination {
val navigator = navigatorProvider.getNavigator<Navigator<*>>(parser.name)
val dest = navigator.createDestination()
dest.onInflate(context, attrs)
val innerDepth = parser.depth + 1
var type: Int
var depth = 0
while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT &&
(parser.depth.also { depth = it } >= innerDepth || type != XmlPullParser.END_TAG)
) {
if (type != XmlPullParser.START_TAG) {
continue
}
if (depth > innerDepth) {
continue
}
val name = parser.name
if (TAG_ARGUMENT == name) {
inflateArgumentForDestination(res, dest, attrs, graphResId)
} else if (TAG_DEEP_LINK == name) {
inflateDeepLink(res, dest, attrs)
} else if (TAG_ACTION == name) {
inflateAction(res, dest, attrs, parser, graphResId)
} else if (TAG_INCLUDE == name && dest is NavGraph) {
res.obtainAttributes(attrs, androidx.navigation.R.styleable.NavInclude).use {
val id = it.getResourceId(androidx.navigation.R.styleable.NavInclude_graph, 0)
dest.addDestination(inflate(id))
}
} else if (dest is NavGraph) {
dest.addDestination(inflate(res, parser, attrs, graphResId))
}
}
return dest
}
- 3 NavHostController: NavController
NavHostFragment 里面
···
onCreate(){
···
navHostController = NavHostController(context)
···
if (graphId != 0) {
// Set from onInflate()
navHostController!!.setGraph(graphId)
}
···
}
// 設置 navigator 是fragment naavigator
···
protected open fun onCreateNavController(navController: NavController) {
navController.navigatorProvider +=
DialogFragmentNavigator(requireContext(), childFragmentManager)
navController.navigatorProvider.addNavigator(createFragmentNavigator())
}
NavHostController 里面
public open fun setGraph(@NavigationRes graphResId: Int) {
setGraph(navInflater.inflate(graphResId), null)
}
···
@MainThread
@CallSuper
public open fun setGraph(graph: NavGraph, startDestinationArgs: Bundle?) {
if (_graph != graph) {
···
_graph = graph
onGraphCreated(startDestinationArgs)
} else {
···
}
}
}
···
private fun onGraphCreated(startDestinationArgs: Bundle?) {
···
// Navigate to the first destination in the graph
// if we haven't deep linked to a destination
navigate(_graph!!, startDestinationArgs, null, null)
···
}
@MainThread
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
···
navigator.onLaunchSingleTop(newEntry) // 調用fragmentNavigator 的方法
···
navigator.navigateInternal(listOf(backStackEntry), navOptions,
navigatorExtras) {
navigated = true
addEntryToBackStack(node, finalArgs, it)
}
}
private fun Navigator<out NavDestination>.navigateInternal(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?,
handler: (backStackEntry: NavBackStackEntry) -> Unit = {}
) {
addToBackStackHandler = handler
navigate(entries, navOptions, navigatorExtras) // 調用fragmentNavigator 的方法
addToBackStackHandler = null
}
- 4 FragmentNavigator
override fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
if (fragmentManager.isStateSaved) {
Log.i(
TAG,
"Ignoring onLaunchSingleTop() call: FragmentManager has already saved its state"
)
return
}
val ft = createFragmentTransaction(backStackEntry, null)
if (state.backStack.value.size > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
fragmentManager.popBackStack(
backStackEntry.id,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
ft.addToBackStack(backStackEntry.id)
}
ft.commit()
// The commit succeeded, update our view of the world
state.onLaunchSingleTop(backStackEntry)
}
private fun createFragmentTransaction(
entry: NavBackStackEntry,
navOptions: NavOptions?
): FragmentTransaction {
val destination = entry.destination as Destination
val args = entry.arguments
var className = destination.className
if (className[0] == '.') {
className = context.packageName + className
}
val frag = fragmentManager.fragmentFactory.instantiate(context.classLoader, className)
frag.arguments = args
val ft = fragmentManager.beginTransaction()
var enterAnim = navOptions?.enterAnim ?: -1
var exitAnim = navOptions?.exitAnim ?: -1
var popEnterAnim = navOptions?.popEnterAnim ?: -1
var popExitAnim = navOptions?.popExitAnim ?: -1
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = if (enterAnim != -1) enterAnim else 0
exitAnim = if (exitAnim != -1) exitAnim else 0
popEnterAnim = if (popEnterAnim != -1) popEnterAnim else 0
popExitAnim = if (popExitAnim != -1) popExitAnim else 0
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}
ft.replace(containerId, frag)
ft.setPrimaryNavigationFragment(frag)
ft.setReorderingAllowed(true)
return ft
}
// NavController 調用
private fun navigate(
entry: NavBackStackEntry,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
val initialNavigation = state.backStack.value.isEmpty()
val restoreState = (
navOptions != null && !initialNavigation &&
navOptions.shouldRestoreState() &&
savedIds.remove(entry.id)
)
if (restoreState) {
// Restore back stack does all the work to restore the entry
fragmentManager.restoreBackStack(entry.id)
state.push(entry)
return
}
val ft = createFragmentTransaction(entry, navOptions)
if (!initialNavigation) {
ft.addToBackStack(entry.id)
}
if (navigatorExtras is Extras) {
for ((key, value) in navigatorExtras.sharedElements) {
ft.addSharedElement(key, value)
}
}
ft.commit()
// The commit succeeded, update our view of the world
state.push(entry)
}