使用流程
根據(jù)自己是kotlin還是java來決定添加哪個
dependencies {
def nav_version = "2.3.0"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
可能還需要一些插件,開頭的地址有寫
Safe Args【非必須的】
要將 Safe Args 添加到您的項目,請在頂級 build.gradle 文件中包含以下 classpath:
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.0-alpha01"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
您還必須應(yīng)用以下兩個可用插件之一。
要生成適用于 Java 或 Java 和 Kotlin 混合模塊的 Java 語言代碼,請將以下行添加到應(yīng)用或模塊的 build.gradle 文件中:
apply plugin: "androidx.navigation.safeargs"
此外,要生成適用于 Kotlin 獨有的模塊的 Kotlin 代碼,請?zhí)砑右韵滦校?/p>
apply plugin: "androidx.navigation.safeargs.kotlin"
根據(jù)遷移到 AndroidX) 文檔,您的 gradle.properties 文件 中必須具有 android.useAndroidX=true。
Navigation 組件旨在用于具有一個主 Activity 和多個 Fragment 目的地的應(yīng)用。主 Activity 與導(dǎo)航圖相關(guān)聯(lián),且包含一個負(fù)責(zé)根據(jù)需要交換目的地的 NavHostFragment。在具有多個 Activity 目的地的應(yīng)用中,每個 Activity 均擁有其自己的導(dǎo)航圖。
開始
- 要向項目添加導(dǎo)航圖,請執(zhí)行以下操作:
在“Project”窗口中,右鍵點擊 res 目錄,然后依次選擇 New > Android Resource File。此時系統(tǒng)會顯示 New Resource File 對話框。
在 File name 字段中輸入名稱,例如“nav_graph”。
從 Resource type 下拉列表中選擇 Navigation,然后點擊 OK。
當(dāng)您添加首個導(dǎo)航圖時,Android Studio 會在 res 目錄內(nèi)創(chuàng)建一個 navigation 資源目錄。該目錄包含您的導(dǎo)航圖資源文件(例如 nav_graph.xml)。


剛建好啥都沒有,自己可以點擊加號添加,下邊列出了所有的fragment和activity,然后左側(cè)那個紅框連接可以跳轉(zhuǎn)到文檔告訴你咋添加.

-
給activity的布局里添加NavHostFragment
這個是系統(tǒng)寫好的Fragment,可以處理導(dǎo)航圖里的fragment,
打開activity的布局文件,左側(cè)palette面板,選擇Containers,右側(cè)選擇NavHostFragment,然后拖動到布局里
image.png
然后出來個彈框,我們可以選擇紅框里我們剛剛建的那個nav_test文件,也可以點上邊的加號新建一個,完事點OK
image.png
然后看下activity的xml文件多了個fragment
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_test" />
請注意以下幾點:
android:name 屬性包含 NavHost 實現(xiàn)的類名稱。
app:navGraph 屬性將 NavHostFragment 與導(dǎo)航圖相關(guān)聯(lián)。導(dǎo)航圖會在此 NavHostFragment 中指定用戶可以導(dǎo)航到的所有目的地。
app:defaultNavHost="true" 屬性確保您的 NavHostFragment 會攔截系統(tǒng)返回按鈕。請注意,只能有一個默認(rèn) NavHost。如果同一布局(例如,雙窗格布局)中有多個主機,請務(wù)必僅指定一個默認(rèn) NavHost
添加fragment
通過點擊navigation文件上邊的那個加號,可以添加新的destination fragment進來,如果已經(jīng)寫好的可以直接在列表里選,如果還沒有,可以直接點擊create new,也可以暫時用個placeholder來代替【編譯沒問題,你要往這里跳轉(zhuǎn)就不行了】設(shè)置start Fragment,就是默認(rèn)加載的那個
在navigation視圖里點擊那個fragment,完事右鍵 ,選擇save as fragment或者點擊上邊的房子圖標(biāo)也行
然后navigation標(biāo)簽下就多了個屬性startDestination
<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_test"
app:startDestination="@id/fragmentNavPreference">
-
添加跳轉(zhuǎn)流程
選中某個fragment的時候,可以看到右側(cè)中間有個圓圈,點擊然后把它拖動到另外 一個fragment上松手,就可以看到兩者直接有條線了,如下圖
image.png
代碼里也會多出一個標(biāo)簽action
id :就是這action的id,之后跳轉(zhuǎn)用到的
destination:就是這個action要跳到的fragment的id
<fragment
android:id="@+id/fragmentNavRoot"
android:name="com.mitac.app2020.sep.nav.FragmentNavRoot"
android:label="FragmentNavRoot"
tools:layout="@layout/fragment_nav_root">
<action
android:id="@+id/action_navRoot_to_navPreference"
app:destination="@id/fragmentNavPreference" />
<action
android:id="@+id/action_fragmentNavRoot_to_fragmentCategory"
app:destination="@id/fragmentCategory" />
</fragment>
-
代碼里如何跳轉(zhuǎn)fragment?
都是通過NavController來控制的,下邊是獲取Controller的方法
image.png
跳轉(zhuǎn)的時候就要用到action里的id了
<fragment
android:id="@+id/fragmentNavRoot"
android:name="com.mitac.app2020.sep.nav.FragmentNavRoot"
android:label="FragmentNavRoot"
tools:layout="@layout/fragment_nav_root">
<action
android:id="@+id/action_navRoot_to_navPreference"
app:destination="@id/fragmentNavPreference" />
<action
android:id="@+id/action_fragmentNavRoot_to_fragmentCategory"
app:destination="@id/fragmentCategory" />
</fragment>
代碼
iv1.setOnClickListener {
findNavController().navigate(R.id.action_navRoot_to_navPreference)
}
iv6.setOnClickListener {
findNavController().navigate(R.id.action_fragmentNavRoot_to_fragmentCategory)
}
//也可以這樣寫
iv3.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_fragmentNavRoot_to_fragmentCategory))
當(dāng)然了navigate后邊參數(shù)有很多的,可以添加bundle啥的
- Safe Args
如果你記不住id的話,又添加了文章開頭的Safe Args 插件的話,那么你build一下工程,然后就多了一些類了
比如我們的fragment類名是FragmentNavRoot,那么就有一個FragmentNavRootDirection的類,這個類下邊有幾個方法,navigation圖里有幾個action就有幾個方法,方法名字貌似就是action的id
如下
val directions=FragmentNavRootDirections.actionFragmentNavRootToFragmentCategory()
findNavController().navigate(directions)
findNavController().navigate(FragmentNavRootDirections.actionNavRootToNavPreference())
- 返回
咋回到上一頁了?默認(rèn)后退鍵就可以回去了,activity里已經(jīng)處理過了。如果我們需要點擊某個按鈕啥的返回,那么也簡單
findNavController().navigateUp()
如果Fragment A 跳到B ,B再跳到C,然后C想直接回到A咋辦?
如下,第一個參數(shù)就是A的fragment id,第二個參數(shù)是為是否A也彈出,這里肯定false了,我們要跳到A的,
如果第一個參數(shù)寫B(tài)的id,那么第二個參數(shù)寫成true倒是剛好.
findNavController().popBackStack(R.id.fragmentA,false)
9.參數(shù)介紹
navigate 跳轉(zhuǎn)到 時候,其實除了第一個action id以外,還有3個參數(shù)可以設(shè)置的,bundle就不說了,這個都會,看下其他兩個
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
@Nullable Navigator.Extras navigatorExtras)
注意一下,這些參數(shù)是可以寫在導(dǎo)航的action標(biāo)簽里的
9.1 NavOptions
一般使用里邊的builder來創(chuàng)建對象,kotlin下有擴展的方法可用,后邊有寫
public static final class Builder {
boolean mSingleTop;
@IdRes
int mPopUpTo = -1;
boolean mPopUpToInclusive;
@AnimRes @AnimatorRes
int mEnterAnim = -1;//四種動畫就不講了
@AnimRes @AnimatorRes
int mExitAnim = -1;
@AnimRes @AnimatorRes
int mPopEnterAnim = -1;
@AnimRes @AnimatorRes
int mPopExitAnim = -1;
/**
* Launch a navigation target as single-top if you are making a lateral navigation
* between instances of the same target (e.g. detail pages about similar data items)
* that should not preserve history.
*
* @param singleTop true to launch as single-top
*/
@NonNull
public Builder setLaunchSingleTop(boolean singleTop) {
mSingleTop = singleTop;
return this;
}
/**
* Pop up to a given destination before navigating. This pops all non-matching destinations
* from the back stack until this destination is found.
*跳轉(zhuǎn)之前先彈出一些Fragment,在destinationId之上的【inclusive決定是否也彈出這個destinationId】
* @param destinationId The destination to pop up to, clearing all intervening destinations.
* @param inclusive true to also pop the given destination from the back stack.
* @return this Builder
* @see NavOptions#getPopUpTo
* @see NavOptions#isPopUpToInclusive
*/
@NonNull
public Builder setPopUpTo(@IdRes int destinationId, boolean inclusive) {
mPopUpTo = destinationId;
mPopUpToInclusive = inclusive;
return this;
}
下邊是kotlin版本的
navOptions { NavOptionsBuilder().popUpTo=R.id.fragmentNavRoot }
java的
val navOptions=NavOptions.Builder().apply {
setPopUpTo(R.id.fragmentNavRoot,false)
}.build()
設(shè)置了popUpTo 我們可以在跳轉(zhuǎn)之前彈出一些Fragment.
9.2 Extras
這個就一個接口,沒有任何方法的
/**
* Interface indicating that this class should be passed to its respective
* {@link Navigator} to enable Navigator specific behavior.
*/
public interface Extras {
}
搜了下,有兩個實現(xiàn)的地方

Fragment傳遞sharedElement
FragmentNavigator.Extras.Builder().addSharedElement(btn_go_search,"test").build()
Activity也差不多,可以傳遞共享元素
val options = ActivityOptions.makeSceneTransitionAnimation(
this,
sharedView,
"shared_element_container" // The transition name to be matched in Activity B.
)
ActivityNavigator.Extras.Builder().setActivityOptions(options).build()
跳轉(zhuǎn)監(jiān)聽
我們導(dǎo)航來回跳轉(zhuǎn)fragment的時候,可能用的是同一個toolbar或者bottomBar之類的,這時候切換fragment的時候我們可能需要修改標(biāo)題啥的,咋辦?activity里添加一個監(jiān)聽即可,監(jiān)聽里可以拿到要跳轉(zhuǎn)的目的地信息以及參數(shù).
findNavController(R.id.fragment).addOnDestinationChangedListener(this)
findNavController(R.id.fragment).removeOnDestinationChangedListener(this)
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
println("changed=======$destination=======${arguments?.size()}")
changeTitle(destination.label?:"...")
}
argument支持的參數(shù)類型

使用safe arg插件傳遞數(shù)據(jù)的demo
上邊的基礎(chǔ)數(shù)據(jù)類型沒啥說的,我們測試下序列化數(shù)據(jù)很枚舉的
注意:
- argType 后邊是類的完整名字,帶路徑的
- 如果默認(rèn)值是null的話,必須添加app:nullable="true"
<action
android:id="@+id/action_fragmentNavRoot_to_fragmentSearchResult"
app:destination="@id/fragmentSearchResult"
app:popUpToInclusive="false">
<argument
android:name="data1"
android:defaultValue="@null"
app:argType="com.xxx.app2020.sep.nav.ArgSerializableTest"
app:nullable="true" />
<argument
android:name="data2"
android:defaultValue="SUN"
app:argType="com.xxx.app2020.sep.nav.ArgEnumTest" />
</action>
<fragment
android:id="@+id/fragmentSearchResult"
android:name="com.mitac.app2020.sep.nav.FragmentSearchResult"
android:label="FragmentSearchResult"
tools:layout="@layout/fragment_search_result">
<argument
android:name="data1"
android:defaultValue="@null"
app:nullable="true"
app:argType="com.mitac.app2020.sep.nav.ArgSerializableTest" />
<argument
android:name="data2"
android:defaultValue="SUN"
app:argType="com.mitac.app2020.sep.nav.ArgEnumTest" />
</fragment>
然后build一下工程,就能看到在build/generated/source/navigation-args/debug/{pakage name}/下自動生成的類了,如下圖
Fragment下帶action的會生成FragmentXXXDirections
Fragment下帶argument的會生成FragmentXXXArgs

然后就可以用了,這些FragmentXXXDirections 下就會多了一些帶argument參數(shù)的方法
傳遞數(shù)據(jù)
val data1=ArgSerializableTest("test",22)
val data2=ArgEnumTest.FLOWER
findNavController().navigate(FragmentNavRootDirections.actionFragmentNavRootToFragmentSearchResult(data1 = data1,data2 = data2))
接收數(shù)據(jù)的Fragment代碼
val args:FragmentSearchResultArgs by navArgs()//kotlin下Fragment的擴展方法
// "${args.data1?.msg} ${args.data1?.age} ${args.data2}"
隨時補充
如下代碼
<?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"
app:startDestination="@id/fragmentHome">
<action
android:id="@+id/go_StepOne"
app:destination="@id/fragmentStepOne" />
<fragment
android:id="@+id/fragmentHome"
android:name="com.charliesong.demo0327.navigation.FragmentHome"
android:label="FragmentHome"
tools:layout="@layout/nav_fragment_home">
</fragment>
<fragment
android:id="@+id/fragmentStepOne"
android:name="com.charliesong.demo0327.navigation.FragmentStepOne"
android:label="FragmentStepOne"
tools:layout="@layout/nav_fragment_step_one">
<argument
android:name="title"
android:defaultValue="just test" />
<argument
android:name="title2"
android:defaultValue="just test2" />
<action
android:id="@+id/fragmentStepOne1"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:destination="@id/fragmentStepTwo" />
</fragment>
<fragment
android:id="@+id/fragmentStepOne1"
android:name="com.charliesong.demo0327.navigation.FragmentStepOne"
android:label="FragmentStepOne"
tools:layout="@layout/nav_fragment_step_one">
<action
android:id="@+id/go_StepTwo"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:destination="@id/fragmentStepTwo" />
</fragment>
<fragment
android:id="@+id/fragmentStepTwo"
android:name="com.charliesong.demo0327.navigation.FragmentStepTwo"
android:label="FragmentStepTwo"
tools:layout="@layout/nav_fragment_step_two">
<action
android:id="@+id/go_StepOne"
app:destination="@id/fragmentStepOne" />
<action
android:id="@+id/go_home"
app:popUpTo="@id/fragmentHome"/>
</fragment>
</navigation>
說明:action標(biāo)簽可以寫在fragment外邊,也可以是里邊,區(qū)別在于。
寫在外邊,這個id哪里都可以用。寫在fragment里邊的,只有這個fragment可以用,其他fragment用這個action的id就會報錯。
問題:
因為我基類里寫了如下的代碼,也就是使用了toolbar的后退鍵
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
android.R.id.home->{
onBackPressed()
//忘了加 return true了,所以引發(fā)了下邊的執(zhí)行了2次后退操作
}
}
return super.onOptionsItemSelected(item)
}
而現(xiàn)在加了navigation以后,我們的fragment也要攔截后退鍵的
override fun onSupportNavigateUp(): Boolean = findNavController(my_nav_host_fragment).navigateUp()
然后就發(fā)現(xiàn),點擊手機上的物理后退鍵是沒有任何問題了。fragment是正常的一次后退一個。
可點擊toolbar上的后退箭頭,就發(fā)現(xiàn)一次退了至少2個fragment。然后去看下了代碼
如下AppCompatActivity里,果然也處理 android.R.id.home,這等于處理了2次。
public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
//super的代碼在下邊,由于忘了寫return true了,所以后退執(zhí)行了2次。
return true;
}
final ActionBar ab = getSupportActionBar();
if (item.getItemId() == android.R.id.home && ab != null &&
(ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
return onSupportNavigateUp();
}
return false;
}
再看下Activity里的代碼
public boolean onMenuItemSelected(int featureId, MenuItem item) {
CharSequence titleCondensed = item.getTitleCondensed();
switch (featureId) {
case Window.FEATURE_OPTIONS_PANEL:
// Put event logging here so it gets called even if subclass
// doesn't call through to superclass's implmeentation of each
// of these methods below
if(titleCondensed != null) {
EventLog.writeEvent(50000, 0, titleCondensed.toString());
}
if (onOptionsItemSelected(item)) {
return true;
}
首先Navigation這個工具類
公開的靜態(tài)方法就是下邊這幾個

然后我們看下獲取
可以看到,是對我們傳入的view,讀取getTag來得到的,如果沒有,就找view的parent。
需要注意的是,如果沒找到,它就直接拋出異常了。所以傳的view要確保有

然后看下設(shè)置,就是把controller設(shè)置為tag
public static void setViewNavController(@NonNull View view,
@Nullable NavController controller) {
view.setTag(R.id.nav_controller_view_tag, controller);
}
看看哪里設(shè)置的,就是系統(tǒng)的NavHostFragment

我們在布局里一般這么寫的,所以如上圖所注釋的,我們的controller就是設(shè)置給了這個fragment的View拉。
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/mobile_navigation"
app:defaultNavHost="true"
/>
另外獲取controller也可以通過如下方法
NavHostFragment里有
public static NavController findNavController(@NonNull Fragment fragment)
或者
public NavController getNavController() {
if (mNavController == null) {
throw new IllegalStateException("NavController is not available before onCreate()");
}
return mNavController;
}
需要注意的是controller是在NavHostFragment的onCreate方法里創(chuàng)建的,所以在這個生命周期之前獲取不到的。
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = getContext();
mNavController = new NavController(context);
mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());



