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 創(chuàng)建Navigation 導航文件

  • 1 在 res 下面創(chuàng)建navigation 目錄
  • 2 右鍵navigation 創(chuàng)建 xml 文件 nav_graph(名字隨便起)
  • 3 如圖所示點擊創(chuàng)建 一個 destination


    圖1 創(chuàng)建destination1

    圖 2 創(chuàng)建destination2

    按照提示添加Fragment博秫。

  • 4 依次同樣的方法添加多個fragment :HomeFragment脚祟,LoginFragmrnt,UserFragment,RegisterFragment浙滤,ContentFragment茶行,如下圖是自動生成躯概。


    圖 3 fragments
  • 5 給這些fragment 增加關系。
    增加下面的線之后左邊就會自動增加Action的標簽畔师。線1 增加的時候左邊對應就會增加一個action娶靡,
    線2 畫出來的時候左邊就對應增加一個2的action。


    圖4 action

    右邊的線都是拖拽就可以的看锉,現(xiàn)在左邊的代碼還都是自動生成的姿锭。

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 跳轉

這種方法是根據(jù)actionId 進行跳轉官方推薦葱她,網(wǎng)上文章大部分都是這樣的使用。
如圖4 里面fragment的里面有 action 叽粹,根據(jù)action Id 進行跳轉览效。
findNavController().navigate(R.id.action_homeFragment_to_loginFragment)却舀。

2 按照deepLink 跳轉

使用較少,網(wǎng)上介紹的也比較少锤灿,但是個人推薦這種方法挽拔。

  • 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 方法參數(shù)
  • 參數(shù) 1 action 和 deepLink 只有第一個參數(shù)不對其他的參數(shù)都是一樣的但校,
    以action 為例
val navOptions = NavOptions.Builder()
                .setLaunchSingleTop(true)         // singleTop
                .setPopUpTo(R.id.homeFragment,    // id 上的fragment 彈出(默認不包含這個id)
                    inclusive = true,             // 是否包含當前的id
                    saveState = true              // 是否保存退出站的狀態(tài)
                )
                .setRestoreState(true)            // 獲取已經(jīng)保存的狀態(tài)
                .build()
//
            findNavController().navigate(
R.id.action_homeFragment_to_loginFragment,   //  參數(shù)1 actionId
                Bundle().apply {
                putString(ARG_PARAM1,"param_from_home")
                putString(ARG_PARAM2,"param_to_login")
            }, //參數(shù)2  bundle
navOptions, //參數(shù)3 
                FragmentNavigatorExtras() // 參數(shù)4 
            )

參數(shù) 2 是 bundle fragment setargument 里面的參數(shù) 給fragment 傳遞參數(shù)的
參數(shù)3 navOptions 主要設置棧的操作(更多的選擇設置)
setLaunchSingleTop 設置 singleTop
setPopUpTo(id螃诅,inclusive ,saveState )
參數(shù) // id 的fragment 彈出(默認不包含這個id)
參數(shù) inclusive // 是否包含當前的id的fragment
參數(shù) saveState // 是否保存退出站的狀態(tài)
setRestoreState 獲取進入的framgnet 的state(之前保存的)
1.1 findNavController().navigate(R.id.action_homeFragment_to_loginFragment)
傳一個resourceId
添加一個 bundle參數(shù)傳遞

  • 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()
}
···
}

根據(jù)name 設置 NavHostFragment View

  • 2 NavHostFragment
    注意 containerFragment.onInflate(context, attrs, null)
    把 FragmentContainerView 的參數(shù) 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)
    }

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亭枷,隨后出現(xiàn)的幾起案子袭艺,更是在濱河造成了極大的恐慌,老刑警劉巖叨粘,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猾编,死亡現(xiàn)場離奇詭異,居然都是意外死亡升敲,警方通過查閱死者的電腦和手機答倡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驴党,“玉大人瘪撇,你說我怎么就攤上這事「圩” “怎么了倔既?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長攘轩。 經(jīng)常有香客問我叉存,道長,這世上最難降的妖魔是什么度帮? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任歼捏,我火速辦了婚禮,結果婚禮上笨篷,老公的妹妹穿的比我還像新娘瞳秽。我一直安慰自己,他們只是感情好率翅,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布练俐。 她就那樣靜靜地躺著,像睡著了一般冕臭。 火紅的嫁衣襯著肌膚如雪腺晾。 梳的紋絲不亂的頭發(fā)上燕锥,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音悯蝉,去河邊找鬼归形。 笑死,一個胖子當著我的面吹牛鼻由,可吹牛的內容都是我干的暇榴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼蕉世,長吁一口氣:“原來是場噩夢啊……” “哼蔼紧!你這毒婦竟也來了?” 一聲冷哼從身側響起狠轻,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤奸例,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哈误,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哩至,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡躏嚎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年蜜自,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卢佣。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡重荠,死狀恐怖,靈堂內的尸體忽然破棺而出虚茶,到底是詐尸還是另有隱情戈鲁,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布嘹叫,位于F島的核電站婆殿,受9級特大地震影響,放射性物質發(fā)生泄漏罩扇。R本人自食惡果不足惜婆芦,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喂饥。 院中可真熱鬧消约,春花似錦、人聲如沸员帮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捞高。三九已至氯材,卻和暖如春渣锦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氢哮。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工泡挺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人命浴。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓娄猫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親生闲。 傳聞我的和親對象是個殘疾皇子媳溺,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容