美文网首页
Navigation

Navigation

作者: freelifes | 来源:发表于2024-03-26 14:20 被阅读0次
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操作,执行切换/跳转页面。

相关文章

网友评论

      本文标题:Navigation

      本文链接:https://www.haomeiwen.com/subject/brodtjtx.html