1、Navigation简介
用于在App界面中切换,包括Activity、fragment、compose、dialog的切换。
2、基本使用
2.1、引入依赖
implementation "androidx.navigation:navigation-compose:2.5.2"
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
implementation "androidx.navigation:navigation-ui-ktx:2.5.2"
2.2、构建Graph
2.2.1、使用xml构建graph,在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/recyclerViewActivity">
<fragment
android:id="@+id/three_fragment"
android:name="com.mahao.customview.fragment.ThreeFragment"
android:label="@string/app_name">
<action
android:id="@+id/action_three_2_one_fragment"
app:destination="@id/one_fragment" />
</fragment>
<activity
android:id="@+id/recyclerViewActivity"
android:name="com.mahao.customview.recycler.RecyclerViewActivity"
android:label="activity_recycler_view"
tools:layout="@layout/activity_recycler_view">
<deepLink
android:autoVerify="false"
app:action="com.sohu.inc"
app:mimeType="/sohu/aa"
app:uri="/sohu/inc"></deepLink>
<argument
android:name="title"
android:defaultValue="integer"
app:argType="string"
app:nullable="false"></argument>
</activity>
<activity
app:route="route://aa.com"
android:id="@+id/b_activity"
android:name="com.mahao.customview.navgation.BActivity"
android:label="navigation_b_activity"></activity>
<composable
android:id="@+id/compose"
android:label="@string/app_name"
app:route="route://aa.com" />
</navigation>
2.2.2、动态构建graph
var graph =
findNavController?.createGraph("route://MainPage//Mine", "route://MainPage//Home") {
addDestination(ActivityNavigatorDestinationBuilder(
findNavController!!.navigatorProvider.getNavigator(
"activity"
), "route://MainPage//Mine"
).build()?.apply {
this.addDeepLink(
NavDeepLink.Builder()
.setAction("com.sohu.inc.com").build()
)
this.setIntent(
Intent(this@NavigationActivity, BActivity::class.java)
)
})
addDestination(
ActivityNavigatorDestinationBuilder(
findNavController!!.navigatorProvider.getNavigator(
"activity"
), R.id.container
).build().setIntent(Intent(this@NavigationActivity, BActivity::class.java))
)
addDestination(
ActivityNavigatorDestinationBuilder(
findNavController!!.navigatorProvider.getNavigator(
"activity"
), "route://MainPage//fragment"
).build()
.setIntent(Intent(this@NavigationActivity, FragmentActivity::class.java))
)
}
findNavController?.setGraph(graph1!!, null)
2.3、创建navController
var navHostController = NavHostController(this);
navHostController?.setGraph(graph1!!, null)
//或者
navHostController?.setGraph(R.navigation.nav_graph)
2.4、执行页面跳转
tvTitle.setOnClickListener {
findNavController?.navigate("route://MainPage//fragment")
}
findViewById<TextView>(R.id.tv_sub_title).setOnClickListener {
findNavController?.navigate(R.id.container)
}
2.5、在compose中使用。
在compose中,与activity中构建不同的是,动态构建graph使用的NavHost。
var navController = rememberNavController()
// navController.setGraph(R.navigation.nav_graph)
NavHost(navController = navController, startDestination = "home") {
composable(route = "profile") {
ProfileScreen(controller = navController)
}
composable(route = "home") {
HomeScreen(controller = navController)
}
}
onClick = {
selected.value = !selected.value
// navController.navigate("main")
// context?.startActivity(Intent(context, ComposeAnimActivity::class.java))
controller?.navigate("profile")
}
3、navigation原理
3.1、解析xml,生成graph
xml解析每个节点node,每个节点node中还包括以下4种元素
3.1.1、根节点类型
navigation : nav_grapg根标签。
fragment :解析fragment
activity : 解析activity
composable : 解析compose
dialog : 解析dialog
3.1.2、子节点
deepLink : 给navDestination设置deep link,类似在acitivity的AndroidManifest中activity配置action
val intent = Intent().apply {
setDataAndType(request.uri, request.mimeType)
action = request.action
}
argument : 向navDestination传递参数。
action : 定义间接的navDestination,支持跳转动画,支持弹出对应fragment。
<action
android:id="@+id/action_three_2_one_fragment"
app:popUpTo="@id/three_fragment"
app:popUpToInclusive="false"
app:destination="@id/one_fragment" />
include : 解析并将其他的graph xm加入到当前graph中。
findNavController?.setGraph(R.navigation.nav_graph)
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.1.3、节点基本属性
所有的destination都包括route、label、id。除此之外,比如fragment还包括name,用于导航到指定的fragment。
public open fun onInflate(context: Context, attrs: AttributeSet) {
context.resources.obtainAttributes(attrs, R.styleable.Navigator).use { array ->
route = array.getString(R.styleable.Navigator_route)
if (array.hasValue(R.styleable.Navigator_android_id)) {
id = array.getResourceId(R.styleable.Navigator_android_id, 0)
idName = getDisplayName(context, id)
}
label = array.getText(R.styleable.Navigator_android_label)
}
}
3.2、执行跳转
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
val navigator = _navigatorProvider.getNavigator<Navigator<NavDestination>>(
node.navigatorName
)
if (navOptions?.shouldLaunchSingleTop() == true &&
node.id == currentBackStackEntry?.destination?.id
) {
unlinkChildFromParent(backQueue.removeLast())
val newEntry = NavBackStackEntry(currentBackStackEntry, finalArgs)
backQueue.addLast(newEntry)
val parent = newEntry.destination.parent
if (parent != null) {
linkChildToParent(newEntry, getBackStackEntry(parent.id))
}
navigator.onLaunchSingleTop(newEntry)
launchSingleTop = true
} else {
// Not a single top operation, so we're looking to add the node to the back stack
val backStackEntry = NavBackStackEntry.create(
context, node, finalArgs, hostLifecycleState, viewModel
)
navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
navigated = true
addEntryToBackStack(node, finalArgs, it)
}
}
}
以ActivityNavigator为例,最终执行context.startActivity(intent)。
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
val intent = Intent(destination.intent)
if (args != null) {
intent.putExtras(args)
val dataPattern = destination.dataPattern
if (!dataPattern.isNullOrEmpty()) {
// Fill in the data pattern with the args to build a valid URI
val data = StringBuffer()
val fillInPattern = Pattern.compile("\\{(.+?)\\}")
val matcher = fillInPattern.matcher(dataPattern)
while (matcher.find()) {
val argName = matcher.group(1)
if (args.containsKey(argName)) {
matcher.appendReplacement(data, "")
data.append(Uri.encode(args[argName].toString()))
}
}
matcher.appendTail(data)
intent.data = Uri.parse(data.toString())
}
}
if (navigatorExtras is Extras) {
intent.addFlags(navigatorExtras.flags)
}
if (hostActivity == null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
if (hostActivity != null) {
val hostIntent = hostActivity.intent
if (hostIntent != null) {
val hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0)
if (hostCurrentId != 0) {
intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId)
}
}
}
val destId = destination.id
intent.putExtra(EXTRA_NAV_CURRENT, destId)
val resources = context.resources
if (navigatorExtras is Extras) {
val activityOptions = navigatorExtras.activityOptions
if (activityOptions != null) {
ActivityCompat.startActivity(context, intent, activityOptions.toBundle())
} else {
context.startActivity(intent)
}
} else {
context.startActivity(intent)
}
if (navOptions != null && hostActivity != null) {
var enterAnim = navOptions.enterAnim
var exitAnim = navOptions.exitAnim
hostActivity.overridePendingTransition(enterAnim, exitAnim)
}
return null
}
4、compose跳转
Navigator.Name("composable")
public class ComposeNavigator : Navigator<Destination>() {
/**
* Get the map of transitions currently in progress from the [state].
*/
internal val transitionsInProgress get() = state.transitionsInProgress
/**
* Get the back stack from the [state].
*/
internal val backStack get() = state.backStack
override fun navigate(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?
) {
entries.forEach { entry ->
state.pushWithTransition(entry)
}
}
override fun createDestination(): Destination {
return Destination(this) { }
}
override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
state.popWithTransition(popUpTo, savedState)
}
}
compose调用navigate,将要启动的Destination对应的NavBackStackEntry加入到回退栈中。当按下返回键时,执行popBackStack将当前NavBackStackEntry从回退栈中移除。
5、navcontroller
5.1、重要方法
执行跳转
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
}
绑定生命周期lifecycle
public open fun setLifecycleOwner(owner: LifecycleOwner)
绑定backPressedDispatcher
public open fun setOnBackPressedDispatcher(dispatcher: OnBackPressedDispatcher) {
}
绑定viewmodelStore
public open fun setViewModelStore(viewModelStore: ViewModelStore) {
}
5.2、fragment中的navController
public fun findNavController(fragment: Fragment): NavController {
var findFragment: Fragment? = fragment
while (findFragment != null) {
if (findFragment is NavHostFragment) {
return findFragment.navHostController as NavController
}
val primaryNavFragment = findFragment.parentFragmentManager
.primaryNavigationFragment
if (primaryNavFragment is NavHostFragment) {
return primaryNavFragment.navHostController as NavController
}
findFragment = findFragment.parentFragment
}
// Try looking for one associated with the view instead, if applicable
val view = fragment.view
if (view != null) {
return Navigation.findNavController(view)
}
// For DialogFragments, look at the dialog's decor view
val dialogDecorView = (fragment as? DialogFragment)?.dialog?.window?.decorView
if (dialogDecorView != null) {
return Navigation.findNavController(dialogDecorView)
}
}
可以看到fragment中获取navController通过fragmentManager从父Fragment中获取。所有的子fragment和父Fragment可以共享一个NavController。
5.3、activity中navcontroller
public fun findNavController(activity: Activity, @IdRes viewId: Int): NavController {
val view = ActivityCompat.requireViewById<View>(activity, viewId)
return findViewNavController(view)
?: throw IllegalStateException(
"Activity $activity does not have a NavController set on $viewId"
)
}
可以看到activity中的navController只能在当前Activity的View层级中寻找,不能跨Activity,所以navigation框架更适合一个Activity,剩下的页面都是Fragment的架构。
5.4、compose中navController
在同一个activity中,compose中的navController创建后,通过composable函数传递navcontroler切换页面。
compose中,一个可组合函数就是一个页面,所以多个页面可以共用一个navController,便于数据传递和生命周期,viewmodelstore的共享。
6、总结
Navigation通过navcontroller切换页面,一个navcontroller对应一个graph。graph中的每个NavDestination节点对应一个Navigator,Navigator封装了startActivity,fragment的transaction操作,执行切换/跳转页面。