1、Navigation簡(jiǎn)介
?? 用于在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斑响、構(gòu)建Graph
?? 2.2.1、使用xml構(gòu)建graph燎猛,在res目錄下新建navigation目錄恋捆,創(chuàng)建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照皆、動(dòng)態(tài)構(gòu)建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重绷、創(chuàng)建navController
var navHostController = NavHostController(this);
navHostController?.setGraph(graph1!!, null)
//或者
navHostController?.setGraph(R.navigation.nav_graph)
??2.4、執(zhí)行頁(yè)面跳轉(zhuǎn)
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中構(gòu)建不同的是瘟滨,動(dòng)態(tài)構(gòu)建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解析每個(gè)節(jié)點(diǎn)node,每個(gè)節(jié)點(diǎn)node中還包括以下4種元素
??3.1.1败玉、根節(jié)點(diǎn)類型
??navigation : nav_grapg根標(biāo)簽敌土。
??fragment :解析fragment
??activity : 解析activity
??composable : 解析compose
??dialog : 解析dialog
??3.1.2、子節(jié)點(diǎn)
??deepLink : 給navDestination設(shè)置deep link运翼,類似在acitivity的AndroidManifest中activity配置action
val intent = Intent().apply {
setDataAndType(request.uri, request.mimeType)
action = request.action
}
??argument : 向navDestination傳遞參數(shù)返干。
??action : 定義間接的navDestination,支持跳轉(zhuǎn)動(dòng)畫(huà),支持彈出對(duì)應(yīng)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加入到當(dāng)前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、節(jié)點(diǎn)基本屬性
??所有的destination都包括route、label癌淮、id躺坟。除此之外,比如fragment還包括name乳蓄,用于導(dǎo)航到指定的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、執(zhí)行跳轉(zhuǎn)
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為例栓袖,最終執(zhí)行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跳轉(zhuǎn)
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調(diào)用navigate裹刮,將要啟動(dòng)的Destination對(duì)應(yīng)的NavBackStackEntry加入到回退棧中音榜。當(dāng)按下返回鍵時(shí),執(zhí)行popBackStack將當(dāng)前NavBackStackEntry從回退棧中移除捧弃。
5赠叼、navcontroller
??5.1、重要方法
執(zhí)行跳轉(zhuǎn)
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通過(guò)fragmentManager從父Fragment中獲取嘴办。所有的子fragment和父Fragment可以共享一個(gè)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只能在當(dāng)前Activity的View層級(jí)中尋找涧郊,不能跨Activity,所以navigation框架更適合一個(gè)Activity眼五,剩下的頁(yè)面都是Fragment的架構(gòu)妆艘。
??5.4、compose中navController
??在同一個(gè)activity中看幼,compose中的navController創(chuàng)建后批旺,通過(guò)composable函數(shù)傳遞navcontroler切換頁(yè)面。
?? compose中诵姜,一個(gè)可組合函數(shù)就是一個(gè)頁(yè)面汽煮,所以多個(gè)頁(yè)面可以共用一個(gè)navController,便于數(shù)據(jù)傳遞和生命周期棚唆,viewmodelstore的共享暇赤。
6、總結(jié)
?? Navigation通過(guò)navcontroller切換頁(yè)面瑟俭,一個(gè)navcontroller對(duì)應(yīng)一個(gè)graph翎卓。graph中的每個(gè)NavDestination節(jié)點(diǎn)對(duì)應(yīng)一個(gè)Navigator,Navigator封裝了startActivity摆寄,fragment的transaction操作失暴,執(zhí)行切換/跳轉(zhuǎn)頁(yè)面坯门。