Navigation

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


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、动态构建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原理
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操作,执行切换/跳转页面。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容