JectPack navigation介绍

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)
    }

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

推荐阅读更多精彩内容