1 简介
Navigation是Android Jetpack中的一个框架,用于在Android应用中的“目标”之间导航,该框架提供一致的 API,“目标”可以是Fragment、Activity或者其他组件。
导航组件由以下三个关键部分组成:
- 导航图: 在一个集中位置包含所有导航相关信息的XML资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
-
NavHost:
显示导航图中目标的空白容器。导航组件包含一个默认NavHost
实现 (NavHostFragment
),可显示Fragment
目标。 -
NavController:
在NavHost
中管理应用导航的对象。当用户在整个应用中移动时,NavController
会安排NavHost
中目标内容的交换。
导航组件提供各种其他优势,包括以下内容:
- 处理Fragment事务。
- 默认情况下,正确处理往返操作。
- 为动画和转换提供标准化资源。
- 实现和处理深层链接。
- 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
- Safe Args - 可在目标之间导航和传递数据时提供类型安全的Gradle插件。
- ViewModel支持 - 您可以将ViewModel的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
- 可以使用Android Studio的Navigation Editor来查看和编辑导航图。
Google Developers 文档:https://developer.android.google.cn/guide/navigation
2 示例代码
以下示例代码是在Android Studio创建的模板代码Bottom Navigation Activity下修改的。
UI就是下图的样子,很简单,1个Activity,3个Fragment。点击HomeFragment中的按钮,能跳转到一个DetailFragment。
首先看activity_main.xml,里边有一个BottomNavigationView
来防止底部的3个tab。上边一个fragment
标签来放置Fragment
。
<?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"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<fragment
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment
标签中引入了NavHostFragment
作为主导航,NavHostFragment
里能获取到NavController
来管理控制其他Fragment
。
fragment
标签有一个属性navGraph
,navGraph
是导航图,导航图文件mobile_navigation.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/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="cn.zhangmushui.navigationsample.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_navigation_home_to_navigation_detail"
app:destination="@id/navigation_detail" />
</fragment>
<fragment
android:id="@+id/navigation_dashboard"
android:name="cn.zhangmushui.navigationsample.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/navigation_notifications"
android:name="cn.zhangmushui.navigationsample.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
<fragment
android:id="@+id/navigation_detail"
android:name="cn.zhangmushui.navigationsample.ui.detail.DetailFragment"
android:label="@string/title_detail"
tools:layout="@layout/fragment_detail" />
</navigation>
mobile_navigation.xml文件位于res/navigation。存放了4个Fragment
,HomeFragment
下有一个action
标签,表示跳转,action
标签下的destination
属性表示要跳转到的位置,这里是跳到DetailFragment
。
点击XML页面右上角的Design,可以切换到导航编辑界面,可以用鼠标按钮进行跳转、主Fragment
修改,以及添加新的Fragment
。
在activity_main.xml中的BottomNavigationView
标签下,menu属性引入了菜单文件bottom_nav_menu.xml,该文件位于res/menu下,每个item
的id和mobile_navigation.xml中每个fragment
的id一一对应:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
然后在Activity
中,将BottomNavigationView
和NavHostFragment
关联起来即可:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
而在HomeFragment
中,点击按钮可以跳转到DetailFragment
,调用的是Navigation.findNavController().navigate()
,navigate
传入的id就是mobile_navigation.xml下fragment
标签下的action
标签的id。
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textHome
homeViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
//跳转到详情页
_binding?.btnDetail?.setOnClickListener {
Navigation.findNavController(it)
.navigate(R.id.action_navigation_home_to_navigation_detail)
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
其他几个类和文件可以下载完整源码查看:https://gitee.com/mushuicode/navigation-sample
3流程图
这是分析完源码之后画出的流程图,可以先看一遍流程图,然后再看下面的源码分析,看完源码之后再结合流程图加深印象。
4 源码分析
以下以navigation 2.4.2进行源码分析,navigation的源码稍微有点复杂,需要花费一定时间去读。
4.1 NavHostFragment.create()
NavHostFragment
实例化的时候,会调用create
方法,在create
方法实例化NavHostFragment
。
public companion object {
...
@JvmOverloads
@JvmStatic
public fun create(
@NavigationRes graphResId: Int,
startDestinationArgs: Bundle? = null
): NavHostFragment {
var b: Bundle? = null
if (graphResId != 0) {
b = Bundle()
b.putInt(KEY_GRAPH_ID, graphResId)
}
if (startDestinationArgs != null) {
if (b == null) {
b = Bundle()
}
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs)
}
//实例化NavHostFragment
val result = NavHostFragment()
if (b != null) {
//设置参数
result.arguments = b
}
return result
}
}
4.2 NavHostFragment.onInflate()
由于NavHostFragment
是通过fragment
标签引入,是一个静态Fragment
,静态Fragment
在onInflate
中初始化,所以NavHostFragment
会先执行onInflate
方法,该方法中会将fragment
标签中的navGraph
属性和defaultNavHost
属性解析出来。
@CallSuper
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 ->
//解析activity_main.xml中fragment标签下的 app:navGraph="@navigation/mobile_navigation"
val graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0
)
if (graphId != 0) {
this.graphId = graphId
}
}
//解析activity_main.xml中fragment标签下的 app:defaultNavHost="true"
//将NavHostFragment设为默认显示的Fragment
context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment).use { array ->
val defaultHost = array.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false)
if (defaultHost) {
defaultNavHost = true
}
}
}
4.3 NavHostFragment.onCreate()
onCreate
方法里实例化了一个NavHostController
,并且传入了mGraphId
。
@CallSuper
public override fun onCreate(savedInstanceState: Bundle?) {
var context = requireContext()
//实例化一个NavHostController
navHostController = NavHostController(context)
navHostController!!.setLifecycleOwner(this)
while (context is ContextWrapper) {
if (context is OnBackPressedDispatcherOwner) {
navHostController!!.setOnBackPressedDispatcher(
(context as OnBackPressedDispatcherOwner).onBackPressedDispatcher
)
// Otherwise, caller must register a dispatcher on the controller explicitly
// by overriding onCreateNavHostController()
break
}
context = context.baseContext
}
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is called
navHostController!!.enableOnBackPressed(
isPrimaryBeforeOnCreate != null && isPrimaryBeforeOnCreate as Boolean
)
isPrimaryBeforeOnCreate = null
navHostController!!.setViewModelStore(viewModelStore)
//向mNavController中添加Navigator
onCreateNavHostController(navHostController!!)
var navState: Bundle? = null
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE)
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
defaultNavHost = true
parentFragmentManager.beginTransaction()
.setPrimaryNavigationFragment(this)
.commit()
}
graphId = savedInstanceState.getInt(KEY_GRAPH_ID)
}
if (navState != null) {
// Navigation controller state overrides arguments
navHostController!!.restoreState(navState)
}
if (graphId != 0) {
// Set from onInflate()
//设置mGraphId,解析mobile_navigation.xml中的信息
navHostController!!.setGraph(graphId)
} else {
// See if it was set by NavHostFragment.create()
val args = arguments
val graphId = args?.getInt(KEY_GRAPH_ID) ?: 0
val startDestinationArgs = args?.getBundle(KEY_START_DESTINATION_ARGS)
if (graphId != 0) {
navHostController!!.setGraph(graphId, startDestinationArgs)
}
}
// We purposefully run this last as this will trigger the onCreate() of
// child fragments, which may be relying on having the NavController already
// created and having its state restored by that point.
super.onCreate(savedInstanceState)
}
4.4 NavHostFragment.onCreateNavController()
@CallSuper
protected open fun onCreateNavHostController(navHostController: NavHostController) {
onCreateNavController(navHostController)
}
@Suppress("DEPRECATION")
@CallSuper
@Deprecated(
"""Override {@link #onCreateNavHostController(NavHostController)} to gain
access to the full {@link NavHostController} that is created by this NavHostFragment."""
)
protected open fun onCreateNavController(navController: NavController) {
//通过NavigatorProvider添加Navigator
navController.navigatorProvider +=
DialogFragmentNavigator(requireContext(), childFragmentManager)
navController.navigatorProvider.addNavigator(createFragmentNavigator())
}
4.5 NavController.setGraph()
@MainThread
@CallSuper
public open fun setGraph(@NavigationRes graphResId: Int) {
setGraph(navInflater.inflate(graphResId), null)
}
调用NavInflater.inflate()
去解析xml,获取导航目的地:
@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()
}
}
然后继续往下走:
@MainThread
@CallSuper
public open fun setGraph(graph: NavGraph, startDestinationArgs: Bundle?) {
if (_graph != graph) {
_graph?.let { previousGraph ->
// Clear all saved back stacks by iterating through a copy of the saved keys,
// thus avoiding any concurrent modification exceptions
val savedBackStackIds = ArrayList(backStackMap.keys)
savedBackStackIds.forEach { id ->
clearBackStackInternal(id)
}
// Pop everything from the old graph off the back stack
//把旧的导航图从栈里面弹出来
popBackStackInternal(previousGraph.id, true)
}
_graph = graph
onGraphCreated(startDestinationArgs)
} else {
for (i in 0 until graph.nodes.size()) {
val newDestination = graph.nodes.valueAt(i)
_graph!!.nodes.replace(i, newDestination)
backQueue.filter { currentEntry ->
currentEntry.destination.id == newDestination?.id
}.forEach { entry ->
entry.destination = newDestination
}
}
}
}
4.6 NavController.onGraphCreated()
@MainThread
private fun onGraphCreated(startDestinationArgs: Bundle?) {
navigatorStateToRestore?.let { navigatorStateToRestore ->
val navigatorNames = navigatorStateToRestore.getStringArrayList(
KEY_NAVIGATOR_STATE_NAMES
)
if (navigatorNames != null) {
for (name in navigatorNames) {
val navigator = _navigatorProvider.getNavigator<Navigator<*>>(name)
val bundle = navigatorStateToRestore.getBundle(name)
if (bundle != null) {
navigator.onRestoreState(bundle)
}
}
}
}
backStackToRestore?.let { backStackToRestore ->
for (parcelable in backStackToRestore) {
val state = parcelable as NavBackStackEntryState
val node = findDestination(state.destinationId)
if (node == null) {
val dest = NavDestination.getDisplayName(
context,
state.destinationId
)
throw IllegalStateException(
"Restoring the Navigation back stack failed: destination $dest cannot be " +
"found from the current destination $currentDestination"
)
}
val entry = state.instantiate(context, node, hostLifecycleState, viewModel)
val navigator = _navigatorProvider.getNavigator<Navigator<*>>(node.navigatorName)
val navigatorBackStack = navigatorState.getOrPut(navigator) {
NavControllerNavigatorState(navigator)
}
backQueue.add(entry)
navigatorBackStack.addInternal(entry)
val parent = entry.destination.parent
if (parent != null) {
linkChildToParent(entry, getBackStackEntry(parent.id))
}
}
updateOnBackPressedCallbackEnabled()
this.backStackToRestore = null
}
// Mark all Navigators as attached
_navigatorProvider.navigators.values.filterNot { it.isAttached }.forEach { navigator ->
val navigatorBackStack = navigatorState.getOrPut(navigator) {
NavControllerNavigatorState(navigator)
}
navigator.onAttach(navigatorBackStack)
}
if (_graph != null && backQueue.isEmpty()) {
val deepLinked =
!deepLinkHandled && activity != null && handleDeepLink(activity!!.intent)
if (!deepLinked) {
// Navigate to the first destination in the graph
// if we haven't deep linked to a destination
//导航到图中的第一个目的地
navigate(_graph!!, startDestinationArgs, null, null)
}
} else {
dispatchOnDestinationChanged()
}
}
4.7 NavController.navigate()
@MainThread
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
navigatorState.values.forEach { state ->
state.isNavigating = true
}
var popped = false
var launchSingleTop = false
var navigated = false
if (navOptions != null) {
if (navOptions.popUpToId != -1) {
popped = popBackStackInternal(
navOptions.popUpToId,
navOptions.isPopUpToInclusive(),
navOptions.shouldPopUpToSaveState()
)
}
}
val finalArgs = node.addInDefaultArgs(args)
// Now determine what new destinations we need to add to the back stack
if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
navigated = restoreStateInternal(node.id, finalArgs, navOptions, navigatorExtras)
} else {
val currentBackStackEntry = currentBackStackEntry
//获取到Navigator
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方法
//以SingleTop的方式启动,支持导航到Activity
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
)
//调用NavController的navigateInternal方法
//以正常方式导航
navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
navigated = true
addEntryToBackStack(node, finalArgs, it)
}
}
}
updateOnBackPressedCallbackEnabled()
navigatorState.values.forEach { state ->
state.isNavigating = false
}
if (popped || navigated || launchSingleTop) {
dispatchOnDestinationChanged()
} else {
updateBackStackLifecycle()
}
}
4.8 Navigator.onLaunchSingleTop()
@Suppress("UNCHECKED_CAST")
public open fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
val destination = backStackEntry.destination as? D ?: return
//调用了Navigator的navigate方法,该方法实现类在ActivityNavigator中
navigate(destination, null, navOptions { launchSingleTop = true }, null)
state.onLaunchSingleTop(backStackEntry)
}
调用Navigator
的4参navigate
方法:
@Suppress("UNUSED_PARAMETER", "RedundantNullableReturnType")
public open fun navigate(
destination: D,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Extras?
): NavDestination? = destination
该navigate
有两处实现,一个在ActivityNavigator
,一个在NoOpNavigator
。
看下ActivityNavigator
中的实现,主要功能就是设置参数、动画之后跳转到Activity
:
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
checkNotNull(destination.intent) {
("Destination ${destination.id} does not have an Intent set.")
}
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()))
} else {
throw IllegalArgumentException(
"Could not find $argName in $args to fill data pattern $dataPattern"
)
}
}
matcher.appendTail(data)
intent.data = Uri.parse(data.toString())
}
}
if (navigatorExtras is Extras) {
intent.addFlags(navigatorExtras.flags)
}
if (hostActivity == null) {
// If we're not launching from an Activity context we have to launch in a new task.
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 (navOptions != null) {
val popEnterAnim = navOptions.popEnterAnim
val popExitAnim = navOptions.popExitAnim
if (
popEnterAnim > 0 && resources.getResourceTypeName(popEnterAnim) == "animator" ||
popExitAnim > 0 && resources.getResourceTypeName(popExitAnim) == "animator"
) {
Log.w(
LOG_TAG,
"Activity destinations do not support Animator resource. Ignoring " +
"popEnter resource ${resources.getResourceName(popEnterAnim)} and " +
"popExit resource ${resources.getResourceName(popExitAnim)} when " +
"launching $destination"
)
} else {
// For use in applyPopAnimationsToPendingTransition()
intent.putExtra(EXTRA_POP_ENTER_ANIM, popEnterAnim)
intent.putExtra(EXTRA_POP_EXIT_ANIM, popExitAnim)
}
}
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
if (
enterAnim > 0 && (resources.getResourceTypeName(enterAnim) == "animator") ||
exitAnim > 0 && (resources.getResourceTypeName(exitAnim) == "animator")
) {
Log.w(
LOG_TAG,
"Activity destinations do not support Animator resource. " +
"Ignoring " + "enter resource " + resources.getResourceName(enterAnim) +
" and exit resource " + resources.getResourceName(exitAnim) + "when " +
"launching " + destination
)
} else if (enterAnim >= 0 || exitAnim >= 0) {
enterAnim = enterAnim.coerceAtLeast(0)
exitAnim = exitAnim.coerceAtLeast(0)
hostActivity.overridePendingTransition(enterAnim, exitAnim)
}
}
// You can't pop the back stack from the caller of a new Activity,
// so we don't add this navigator to the controller's back stack
return null
}
4.9 NavController.navigateInternal()
private fun Navigator<out NavDestination>.navigateInternal(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?,
handler: (backStackEntry: NavBackStackEntry) -> Unit = {}
) {
addToBackStackHandler = handler
//导航
navigate(entries, navOptions, navigatorExtras)
addToBackStackHandler = null
}
调用了Navigator
的3参navigate
方法:
@Suppress("UNCHECKED_CAST")
public open fun navigate(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?
) {
entries.asSequence().map { backStackEntry ->
val destination = backStackEntry.destination as? D ?: return@map null
//调用Navigator的naviagte
val navigatedToDestination = navigate(
destination, backStackEntry.arguments, navOptions, navigatorExtras
)
when (navigatedToDestination) {
null -> null
destination -> backStackEntry
else -> {
state.createBackStackEntry(
navigatedToDestination,
navigatedToDestination.addInDefaultArgs(backStackEntry.arguments)
)
}
}
}.filterNotNull().forEach { backStackEntry ->
state.push(backStackEntry)
}
}
接着调用Navigator
的4参navigate
方法:
@Suppress("UNUSED_PARAMETER", "RedundantNullableReturnType")
public open fun navigate(
destination: D,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Extras?
): NavDestination? = destination
4.10 FragmentNavigator.navigate()
Navigator.navigate()
具体实现在FragmentNavigator
中,通过反射获取到目标Fragment
的实例,然后设置动画和参数:
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
}
//反射获取到Fragment实例
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)
}
}
4.11 FragmentFactory.instantiate()
instantiate
方法中,通过反射拿到Fragment
实例:
@NonNull
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
try {
Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
return cls.getConstructor().newInstance();
} catch (java.lang.InstantiationException e) {
...
}
}
@NonNull
public static Class<? extends Fragment> loadFragmentClass(@NonNull ClassLoader classLoader,
@NonNull String className) {
try {
Class<?> clazz = loadClass(classLoader, className);
return (Class<? extends Fragment>) clazz;
} catch (ClassNotFoundException e) {
...
}
}
private static Class<?> loadClass(@NonNull ClassLoader classLoader,
@NonNull String className) throws ClassNotFoundException {
SimpleArrayMap<String, Class<?>> classMap = sClassCacheMap.get(classLoader);
if (classMap == null) {
classMap = new SimpleArrayMap<>();
sClassCacheMap.put(classLoader, classMap);
}
Class<?> clazz = classMap.get(className);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(className, false, classLoader);
classMap.put(className, clazz);
}
return clazz;
}
完整示例代码:https://gitee.com/mushuicode/navigation-sample
关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。