之前已经了解了navigation的基本用法,现在来看一下他的源码,知其然更要知其所以然,这样在遇到问题的时候才能知道怎么处理。以fragment中实现跳转来看一下源码中如何实现了跳转的。
fragment中使用
findNavController().navigate(R.id.xxx)
点进去看看实现,在NavController中经过一系列重载方法,最终调用了
@MainThread
public open fun navigate(
@IdRes resId: Int,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
var finalNavOptions = navOptions
val currentNode = (
if (backQueue.isEmpty())
_graph
else
backQueue.last().destination
) ?: throw IllegalStateException("no current navigation node")
@IdRes
var destId = resId
val navAction = currentNode.getAction(resId)
var combinedArgs: Bundle? = null
...
val node = findDestination(destId)
...
navigate(node, combinedArgs, finalNavOptions, navigatorExtras)
}
这一步做的比较简单,就是解析参数,并调用了
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
...
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 = {}
) {
navigate(entries, navOptions, navigatorExtras)
}
这里调用的是Navigator中的方法,在fragment里面的跳转中使用的是FragmentNavigator的方法,
override fun navigate(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
if (fragmentManager.isStateSaved) {
Log.i(
TAG, "Ignoring navigate() call: FragmentManager has already saved its state"
)
return
}
for (entry in entries) {
navigate(entry, navOptions, navigatorExtras)
}
}
最终调用为
private fun navigate(
entry: NavBackStackEntry,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
val backStack = state.backStack.value
val initialNavigation = backStack.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 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)
@IdRes val destId = destination.id
// TODO Build first class singleTop behavior for fragments
val isSingleTopReplacement = (
navOptions != null && !initialNavigation &&
navOptions.shouldLaunchSingleTop() &&
backStack.last().destination.id == destId
)
val isAdded = when {
initialNavigation -> {
true
}
isSingleTopReplacement -> {
// Single Top means we only want one instance on the back stack
if (backStack.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(
entry.id,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
ft.addToBackStack(entry.id)
}
false
}
else -> {
ft.addToBackStack(entry.id)
true
}
}
if (navigatorExtras is Extras) {
for ((key, value) in navigatorExtras.sharedElements) {
ft.addSharedElement(key, value)
}
}
ft.setReorderingAllowed(true)
ft.commit()
// The commit succeeded, update our view of the world
if (isAdded) {
state.push(entry)
}
}
跳转的核心代码为ft.replace(containerId, frag),这里的FragmentManger为NavHostFragment创建的
@Deprecated("Use {@link #onCreateNavController(NavController)}")
protected open fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination> {
return FragmentNavigator(requireContext(), childFragmentManager, containerId)
}
也就是说,这里的FragmentManger为childFragmentManager,看到这里就明白了,NavHostFragment为占位用的,我们自己写的Fragment由NavHostFragment控制的子fragment,内部使用replace方式实现跳转效果。