Navigation

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


navigation
<?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原理
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è)面坯门。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逗扒,隨后出現(xiàn)的幾起案子古戴,更是在濱河造成了極大的恐慌,老刑警劉巖矩肩,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件现恼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡黍檩,警方通過(guò)查閱死者的電腦和手機(jī)叉袍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刽酱,“玉大人喳逛,你說(shuō)我怎么就攤上這事】美铮” “怎么了润文?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)殿怜。 經(jīng)常有香客問(wèn)我典蝌,道長(zhǎng),這世上最難降的妖魔是什么头谜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任骏掀,我火速辦了婚禮,結(jié)果婚禮上乔夯,老公的妹妹穿的比我還像新娘砖织。我一直安慰自己,他們只是感情好末荐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布代嗤。 她就那樣靜靜地躺著邦蜜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒙秒。 梳的紋絲不亂的頭發(fā)上妹笆,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天块请,我揣著相機(jī)與錄音,去河邊找鬼拳缠。 笑死墩新,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窟坐。 我是一名探鬼主播海渊,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼绵疲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了臣疑?” 一聲冷哼從身側(cè)響起盔憨,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讯沈,沒(méi)想到半個(gè)月后郁岩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缺狠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年问慎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挤茄。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝴乔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驮樊,到底是詐尸還是另有隱情薇正,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布囚衔,位于F島的核電站挖腰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏练湿。R本人自食惡果不足惜猴仑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肥哎。 院中可真熱鬧辽俗,春花似錦、人聲如沸篡诽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杈女。三九已至朱浴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間达椰,已是汗流浹背翰蠢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啰劲,地道東北人梁沧。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蝇裤,于是被迫代替她去往敵國(guó)和親廷支。 傳聞我的和親對(duì)象是個(gè)殘疾皇子频鉴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容