前言
這可以說(shuō)是 Jetpack Navigation
胎教級(jí)文章,從 使用入門 到 常見(jiàn)問(wèn)題 再到 示例項(xiàng)目 一篇文章全部包括猛计。
Navigation及其用途
Navigation
是 Android Jetpack
組件之一,主要是用于 Fragment
路由導(dǎo)航的框架爆捞,通過(guò) Navigation
我們可以設(shè)計(jì)出單 Activity
應(yīng)用架構(gòu)奉瘤。
Navigation
由以下三個(gè)關(guān)鍵部分組成:
-
NavHost
:顯示Fragment
的容器。 -
NavController
:在NavHost
中管理應(yīng)用導(dǎo)航的對(duì)象煮甥。 - 導(dǎo)航圖:配置所有導(dǎo)航相關(guān)信息的 XML 資源毛好。跟我們常用的
AndroidManifest.xml
類似望艺。
所以 Navigation
的使用即在 導(dǎo)航圖 配置 Fragment
相關(guān)信息,再告訴 NavController
導(dǎo)航至特定目標(biāo)肌访, NavController
便會(huì)在 NavHost
中顯示相應(yīng)目標(biāo)找默。
使用入門
請(qǐng)向應(yīng)用的 build.gradle
文件添加以下依賴項(xiàng):
dependencies {
val nav_version = "2.3.5"
// Kotlin
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
}
添加導(dǎo)航圖,請(qǐng)執(zhí)行以下操作:
- 在“Project”窗口中吼驶,右鍵點(diǎn)擊
res
目錄惩激,然后依次選擇 New > Android Resource File。此時(shí)系統(tǒng)會(huì)顯示 New Resource File 對(duì)話框蟹演。 - 在 File name 字段中輸入名稱风钻,例如“nav_graph”。
- 從 Resource type 下拉列表中選擇 Navigation酒请,然后點(diǎn)擊 OK骡技。
當(dāng)您添加首個(gè)導(dǎo)航圖時(shí),Android Studio 會(huì)在 res
目錄內(nèi)創(chuàng)建一個(gè) navigation
資源目錄羞反。該目錄包含您的導(dǎo)航圖資源文件(例如 nav_graph.xml
)布朦。
<?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"
android:id="@+id/nav_graph">
</navigation>
向 Activity 添加 NavHost
請(qǐng)注意以下幾點(diǎn):
-
android:name
屬性包含NavHost
實(shí)現(xiàn)的類名稱。 -
app:navGraph
屬性將NavHostFragment
與導(dǎo)航圖相關(guān)聯(lián)昼窗。導(dǎo)航圖會(huì)在此NavHostFragment
中指定用戶可以導(dǎo)航到的所有目的地是趴。 -
app:defaultNavHost="true"
屬性確保您的NavHostFragment
會(huì)攔截系統(tǒng)返回按鈕。請(qǐng)注意,只能有一個(gè)默認(rèn)NavHost
。如果同一布局(例如捂龄,雙窗格布局)中有多個(gè)宿主环础,請(qǐng)務(wù)必僅指定一個(gè)默認(rèn)NavHost
。
<?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.appcompat.widget.Toolbar
.../>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
.../>
</androidx.constraintlayout.widget.ConstraintLayout>
向?qū)Ш綀D添加并導(dǎo)航到目的地
在導(dǎo)航圖中,操作由 <action>
元素表示。操作至少應(yīng)包含自己的 ID 和用戶應(yīng)轉(zhuǎn)到的目的地的 ID。
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="@string/label_blank"
tools:layout="@layout/fragment_blank" >
<action
android:id="@+id/action_blankFragment_to_blankFragment2"
app:destination="@id/blankFragment2" />
</fragment>
<fragment
android:id="@+id/blankFragment2"
android:name="com.example.cashdog.cashdog.BlankFragment2"
android:label="@string/label_blank_2"
tools:layout="@layout/fragment_blank_fragment2" />
</navigation>
導(dǎo)航到目的地是使用 NavController
完成的温赔,它是一個(gè)在 NavHost
中管理應(yīng)用導(dǎo)航的對(duì)象。通過(guò) navigate(int)
接受操作或目的地的資源 ID 作為參數(shù)帅刀。
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
navController.navigate(R.id.action_blankFragment_to_blankFragment2)
導(dǎo)航圖規(guī)則
- 導(dǎo)航圖之間可以通過(guò)
id
跳轉(zhuǎn)到對(duì)應(yīng)導(dǎo)航圖的startDestination
- 同個(gè)導(dǎo)航圖之內(nèi)可以通過(guò)
action
互相跳轉(zhuǎn) - 不同導(dǎo)航圖之間可以通過(guò)深層鏈接跳轉(zhuǎn)
以上就是 Navigation
的基本使用让腹,其他細(xì)節(jié)可到官網(wǎng)查看Navigation 組件使用入門 | Android Developers
常見(jiàn)問(wèn)題
通過(guò)上面的介紹我們對(duì) Navigation
有了一定的了解,但當(dāng)你按照文檔進(jìn)行開(kāi)發(fā)時(shí)扣溺,便會(huì)發(fā)現(xiàn) Navigation
存在的一些小問(wèn)題骇窍。
因?yàn)?Navigation
是通過(guò) replace
的方式去替換 Fragment
,即每次 Fragment
的切換都是重新創(chuàng)建的 锥余,而問(wèn)題也隨之而來(lái)腹纳。
重復(fù)請(qǐng)求數(shù)據(jù)
既然我們的項(xiàng)目用了 Navigation
,那么 Jetpack
全家桶肯定是一個(gè)不能少的了。 Fragment
的重建并不會(huì)使ViewModel
被銷毀嘲恍,所以 LiveData
的數(shù)據(jù)依然保存著足画,頁(yè)面重建后會(huì)把數(shù)據(jù)重新渲染到頁(yè)面上,所以在視覺(jué)上我們根本感覺(jué)不到被頁(yè)面被重建了佃牛,但是相應(yīng)的初始化邏輯會(huì)繼續(xù)執(zhí)行會(huì)重新請(qǐng)求數(shù)據(jù)淹辞,這就造成了資源的浪費(fèi)。既然知道了原因俘侠,那如何避免這個(gè)問(wèn)題呢象缀?
最簡(jiǎn)單的方法是在請(qǐng)求數(shù)據(jù)前加個(gè)空判斷,代碼如下:
if (viewModel.data.value == null) {
viewModel.getData()
}
乍一看會(huì)覺(jué)得這方案好low呀爷速,請(qǐng)大家先別著急聽(tīng)我慢慢道來(lái)央星。在此之前我亦是采用過(guò)其他方案,比如對(duì) LiveData
進(jìn)行包裝惫东,隨著業(yè)務(wù)需求的不斷變化莉给,你就要去維護(hù)它這是不能接受的。最終采用上述的方案廉沮,越簡(jiǎn)單越不會(huì)出錯(cuò)(UNIX設(shè)計(jì)哲學(xué))颓遏。
過(guò)渡動(dòng)畫卡頓
導(dǎo)致過(guò)渡動(dòng)畫卡頓的原因當(dāng)然也是 Fragment
重建咯,Fragment
重建后需要重新渲染頁(yè)面废封,卡那么一下也是正持莶矗現(xiàn)象丧蘸。既然知道了原因漂洋,那如何避免這個(gè)問(wèn)題呢?
最簡(jiǎn)單的方法是在動(dòng)畫執(zhí)行完成后渲染頁(yè)面力喷,代碼如下:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.postDelayed({
initLoad()
}, delayedLoad) //delayedLoad為設(shè)置的過(guò)度動(dòng)畫時(shí)間
}
依舊是UNIX設(shè)計(jì)哲學(xué)刽漂,當(dāng)然上述的方案并不是唯一解,其他方案請(qǐng)自行搜索學(xué)習(xí)哈??
(ps:官方為什么不解決上面這些問(wèn)題呢弟孟?個(gè)人的猜測(cè)是官方還沒(méi)有找一個(gè)優(yōu)雅的解決方案贝咙,期待后續(xù)的版本的優(yōu)化吧 ??)
示例項(xiàng)目
- github: fragmject
Thanks
以上就是本篇文章的全部?jī)?nèi)容,如有問(wèn)題歡迎指出拂募,我們一起進(jìn)步庭猩。
如果喜歡的話希望點(diǎn)個(gè)贊吧,您的鼓勵(lì)是我前進(jìn)的動(dòng)力陈症。
謝謝~~