美文网首页
JectPack navigation介绍

JectPack navigation介绍

作者: 无敌之神将 | 来源:发表于2022-12-26 14:30 被阅读0次

    JetPack Navigation 介绍

    Jetpack Navigation 是啥

    Jetpack Navigation 是 Jetpack 里面的一个依赖库,用来对Fragment 页面的导航和管理的。

    使用

    1 引用

    // Kotlin
      implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
      implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
    

    2 创建Navigation 导航文件

    • 1 在 res 下面创建navigation 目录
    • 2 右键navigation 创建 xml 文件 nav_graph(名字随便起)
    • 3 如图所示点击创建 一个 destination


      图1 创建destination1
      图 2 创建destination2

      按照提示添加Fragment。

    • 4 依次同样的方法添加多个fragment :HomeFragment,LoginFragmrnt,UserFragment,RegisterFragment,ContentFragment,如下图是自动生成。


      图 3 fragments
    • 5 给这些fragment 增加关系。
      增加下面的线之后左边就会自动增加Action的标签。线1 增加的时候左边对应就会增加一个action,
      线2 画出来的时候左边就对应增加一个2的action。


      图4 action

      右边的线都是拖拽就可以的,现在左边的代码还都是自动生成的。

    3把navigation 图展示出来

    activity 的xml 把navigation 加入进来,下面是mainactivity 的布局。

    <?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"
       xmlns:tools="http://schemas.android.com/tools"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       tools:context=".MainActivity">
       <androidx.fragment.app.FragmentContainerView
           android:id="@+id/nav_host_fragment"
           app:layout_constraintTop_toTopOf="parent"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintBottom_toBottomOf="parent"
           android:name="androidx.navigation.fragment.NavHostFragment"
           app:defaultNavHost="true"
           app:navGraph="@navigation/nav_graph"
           android:layout_width="0dp"
           android:layout_height="0dp"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    FragmentContainerView 是用来展示navigation 的布局。
    注意 name字段 、 navGraph 字段和defaultNavHost 字段。
    name 字段是加载的fragment 里面用来处理 destination 的逻辑跳转的容器。
    navGraph 字段是用来 指示navigation。
    defaultNavHost 字段用来表示 返回键单独处理不,默认单独处理走fragment的栈的地方。
    这样MainActivity 就展示的是nav_graph 的startDestination 也就是homeFragment。

    4 navigation 之间的跳转

    下面这一行就是最简单的跳转方法

    view.findViewById<Button>(R.id.btn_login).setOnClickListener { 
                findNavController().navigate(R.id.action_homeFragment_to_loginFragment)
            }
    

    findNavController() 是Fragment 的拓展方法。

    package androidx.navigation.fragment
    public fun Fragment.findNavController(): NavController =
        NavHostFragment.findNavController(this)
    

    所有的对fragment的管理都是通过NavController 管理的。
    返回: findNavController().popBackStack() 或者 findNavController().navigateUp()
    // findNavController().popBackStack() 可以越级back
    // findNavController().navigateUp() 在一定的条件下可以finish activity

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            view.findViewById<Button>(R.id.btn_back).setOnClickListener {
    //            findNavController().popBackStack()  
                findNavController().navigateUp()
            }
        }
    

    5 NavController

    NavController 是核心类

    navigate 方法

    1 按照 action 跳转

    这种方法是根据actionId 进行跳转官方推荐,网上文章大部分都是这样的使用。
    如图4 里面fragment的里面有 action ,根据action Id 进行跳转。
    findNavController().navigate(R.id.action_homeFragment_to_loginFragment)。

    2 按照deepLink 跳转

    使用较少,网上介绍的也比较少,但是个人推荐这种方法。

    • 1 像上面图三 一样添加fragment
    • 2 不给fragment 添加关系给他们手动添加 deepLink
       <fragment
            android:id="@+id/DFragment1"
            android:name="com.eswincomputing.navigationdemo.deeplink.DFragment1"
            android:label="fragment_d1"
            tools:layout="@layout/fragment_d1" >
            <deepLink
                android:id="@+id/deep_link_d1"
                app:uri="android-app://androidx.navigation/d1"/>
        </fragment>
    

    注意 uri 里面 android-app://androidx.navigation/ 这块的字符串要写固定。
    用法
    findNavController().navigate("d1")

        findNavController().navigate("d1")
        ....
       @JvmOverloads
        public fun navigate(
            route: String,
            navOptions: NavOptions? = null,
            navigatorExtras: Navigator.Extras? = null
        ) {
            navigate(
                NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(), navOptions,
                navigatorExtras
            )
        }
    
    ···
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
            public fun createRoute(route: String?): String =
                if (route != null) "android-app://androidx.navigation/$route" else ""
    
    
    3 navigate 方法参数
    • 参数 1 action 和 deepLink 只有第一个参数不对其他的参数都是一样的,
      以action 为例
    val navOptions = NavOptions.Builder()
                    .setLaunchSingleTop(true)         // singleTop
                    .setPopUpTo(R.id.homeFragment,    // id 上的fragment 弹出(默认不包含这个id)
                        inclusive = true,             // 是否包含当前的id
                        saveState = true              // 是否保存退出站的状态
                    )
                    .setRestoreState(true)            // 获取已经保存的状态
                    .build()
    //
                findNavController().navigate(
    R.id.action_homeFragment_to_loginFragment,   //  参数1 actionId
                    Bundle().apply {
                    putString(ARG_PARAM1,"param_from_home")
                    putString(ARG_PARAM2,"param_to_login")
                }, //参数2  bundle
    navOptions, //参数3 
                    FragmentNavigatorExtras() // 参数4 
                )
    

    参数 2 是 bundle fragment setargument 里面的参数 给fragment 传递参数的
    参数3 navOptions 主要设置栈的操作(更多的选择设置)
    setLaunchSingleTop 设置 singleTop
    setPopUpTo(id,inclusive ,saveState )
    参数 // id 的fragment 弹出(默认不包含这个id)
    参数 inclusive // 是否包含当前的id的fragment
    参数 saveState // 是否保存退出站的状态
    setRestoreState 获取进入的framgnet 的state(之前保存的)
    1.1 findNavController().navigate(R.id.action_homeFragment_to_loginFragment)
    传一个resourceId
    添加一个 bundle参数传递

    • 2 返回
      popBackStack() 正常一个一个退出
      popBackStack(
      @IdRes destinationId: Int,
      inclusive: Boolean,
      saveState: Boolean
      )
      destinationId 目的地fragmentid 退出这个id 上面的fragment
      inclusive 是否包含这个id 目的地id
      saveState 推出的fragment 是否保存state

    默认加载fragment 逻辑
    FragmentContainerView constructer 方法 --》 NavHostFragment
    oncreate 设置属性 和 graphID
    FragmentContainerView onInflate --》 NavHostFragment 的 onInflate 获取graphID 。

    NavHostFragment oncreate --》
    navController!!.setGraph(graphId) --》 navInflater.inflate(graphResId) -》
    NavHostController--onGraphCreated -- 》NavHostController----navigate --》 FragmentNavigator —— onLaunchSingleTop,navigateInternal ——》

    关键类

    • NavController (NavHostController)
      navigate 和pop 跳转退出

    • FragmentContainerView
      activity 里面包含navigationFragment 的View

      <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host_fragment"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph_deeplink"
            android:layout_width="0dp"
            android:layout_height="0dp"/>
    
    

    注意name
    android:name="androidx.navigation.fragment.NavHostFragment"

    FragmentContainerView {
    constructer(){
    ···
      val containerFragment: Fragment =
                    fm.fragmentFactory.instantiate(context.classLoader, name)
                containerFragment.onInflate(context, attrs, null)
                fm.beginTransaction()
                    .setReorderingAllowed(true)
                    .add(this, containerFragment, tag)
                    .commitNowAllowingStateLoss()
    }
    ···
    }
    

    根据name 设置 NavHostFragment View

    • 2 NavHostFragment
      注意 containerFragment.onInflate(context, attrs, null)
      把 FragmentContainerView 的参数 attrs 都传递给 NavHostFragment 了
    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 ->
               val graphId = navHost.getResourceId(
                   androidx.navigation.R.styleable.NavHost_navGraph, 0
               )
               if (graphId != 0) {
                   this.graphId = graphId
               }
           }
           context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment).use { array ->
               val defaultHost = array.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false)
               if (defaultHost) {
                   defaultNavHost = true
               }
           }
       }
    
    • 4 NavInflater 解析 graph
     @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()
            }
        }
    
     @Throws(XmlPullParserException::class, IOException::class)
        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 NavHostController: NavController
    NavHostFragment 里面
    ···
      onCreate(){
    ···
    navHostController = NavHostController(context)
    ···
     if (graphId != 0) {
                // Set from onInflate()
                navHostController!!.setGraph(graphId)
            }
    ···
    }
    
    // 设置 navigator 是fragment naavigator
    ··· 
     protected open fun onCreateNavController(navController: NavController) {
            navController.navigatorProvider +=
                DialogFragmentNavigator(requireContext(), childFragmentManager)
            navController.navigatorProvider.addNavigator(createFragmentNavigator())
        }
    
    NavHostController 里面
    public open fun setGraph(@NavigationRes graphResId: Int) {
            setGraph(navInflater.inflate(graphResId), null)
        }
    ···
    @MainThread
        @CallSuper
        public open fun setGraph(graph: NavGraph, startDestinationArgs: Bundle?) {
            if (_graph != graph) {
               ···
                _graph = graph
                onGraphCreated(startDestinationArgs)
            } else {
                ···
                }
            }
        }
    ···
    private fun onGraphCreated(startDestinationArgs: Bundle?) {
    ···
                    // Navigate to the first destination in the graph
                    // if we haven't deep linked to a destination
                    navigate(_graph!!, startDestinationArgs, null, null)
    ···
    }
    
     @MainThread
        private fun navigate(
            node: NavDestination,
            args: Bundle?,
            navOptions: NavOptions?,
            navigatorExtras: Navigator.Extras?
        ) {
    ···
          navigator.onLaunchSingleTop(newEntry) // 调用fragmentNavigator 的方法
    ···
          navigator.navigateInternal(listOf(backStackEntry), navOptions, 
                           navigatorExtras) {
                        navigated = true
                        addEntryToBackStack(node, finalArgs, it)
                    }
    }
    
    private fun Navigator<out NavDestination>.navigateInternal(
            entries: List<NavBackStackEntry>,
            navOptions: NavOptions?,
            navigatorExtras: Navigator.Extras?,
            handler: (backStackEntry: NavBackStackEntry) -> Unit = {}
        ) {
            addToBackStackHandler = handler
            navigate(entries, navOptions, navigatorExtras) // 调用fragmentNavigator 的方法
            addToBackStackHandler = null
        }
    
    • 4 FragmentNavigator
    override fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
            if (fragmentManager.isStateSaved) {
                Log.i(
                    TAG,
                    "Ignoring onLaunchSingleTop() call: FragmentManager has already saved its state"
                )
                return
            }
            val ft = createFragmentTransaction(backStackEntry, null)
            if (state.backStack.value.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(
                    backStackEntry.id,
                    FragmentManager.POP_BACK_STACK_INCLUSIVE
                )
                ft.addToBackStack(backStackEntry.id)
            }
            ft.commit()
            // The commit succeeded, update our view of the world
            state.onLaunchSingleTop(backStackEntry)
        }
    
    
    
    private fun createFragmentTransaction(
            entry: NavBackStackEntry,
            navOptions: NavOptions?
        ): FragmentTransaction {
            val destination = entry.destination as Destination
            val args = entry.arguments
            var className = destination.className
            if (className[0] == '.') {
                className = context.packageName + className
            }
            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)
            ft.setReorderingAllowed(true)
            return ft
        }
     
    // NavController  调用
     private fun navigate(
            entry: NavBackStackEntry,
            navOptions: NavOptions?,
            navigatorExtras: Navigator.Extras?
        ) {
            val initialNavigation = state.backStack.value.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 ft = createFragmentTransaction(entry, navOptions)
    
            if (!initialNavigation) {
                ft.addToBackStack(entry.id)
            }
    
            if (navigatorExtras is Extras) {
                for ((key, value) in navigatorExtras.sharedElements) {
                    ft.addSharedElement(key, value)
                }
            }
            ft.commit()
            // The commit succeeded, update our view of the world
            state.push(entry)
        }
    
    

    相关文章

      网友评论

          本文标题:JectPack navigation介绍

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