多模組專案的導覽最佳做法

導覽圖可包含以下項目的任意組合:

  • 單一目的地,例如 <fragment> 目的地。
  • 一個巢狀圖,當中包含一組相關目的地。
  • 一個 <include> 元素,可讓您以類似巢狀結構的方式嵌入另一個導覽圖檔案。

這種靈活性可讓您結合較小的導覽圖,構成應用程式的完整導覽圖,即使這些較小的導覽圖是由不同的程式庫模組提供也沒問題。

針對這個主題中的範例,每個程式庫模組都著重於單一功能,並提供單一導覽圖,當中包含實作該功能所需的所有目的地。在正式版應用程式中,您可能會有許多較低層級的子模組,其為這個較高層級程式庫模組的實作詳細資料。這些程式庫模組全都直接或間接包含在 app 模組中。這份文件中使用的範例多模組應用程式結構如下:

範例應用程式的應用程式架構
範例應用程式的起始目的地
圖 1. 範例應用程式的應用程式架構和起始目的地。

每個程式庫模組都是獨立單元,具有專屬導覽圖和目的地。app 模組依附各個程式庫模組,因此您必須將程式庫模組做為實作詳細資料新增至其 build.gradle 檔案中,如下所示:

dependencies {
    ...
    implementation project(":list")
    implementation project(":favorites")
    implementation project(":settings")

app 模組的功用

app 模組負責為應用程式提供完整圖表,並將 NavHost 新增至 UI。在 app 模組的導覽圖中,您可以使用 <include> 參照程式庫圖表。雖然 <include> 的功用與巢狀圖表相同,但 <include> 支援其他專案模組或程式庫專案的圖表,如以下範例所示:

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/list_nav_graph">

    <include app:graph="@navigation/list_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />
</navigation>

將程式庫納入頂層導覽圖後,您可以視需要瀏覽至程式庫圖表。舉例來說,您可以建立一項動作,以便從導覽圖中的某個片段瀏覽至設定圖表,如下所示:

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/list_nav_graph">

    <include app:graph="@navigation/list_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />

    <fragment
        android:id="@+id/random_fragment"
        android:name="com.example.android.RandomFragment"
        android:label="@string/fragment_random" >
        <!-- Launch into Settings Navigation Graph -->
        <action
            android:id="@+id/action_random_fragment_to_settings_nav_graph"
            app:destination="@id/settings_nav_graph" />
    </fragment>
</navigation>

如有多個程式庫模組必須參照一組常用目的地 (例如登入圖表),請勿將這些常見目的地加入每個程式庫模組的導覽圖中,而應改為將這些常用目的地新增至 app 模組的導覽圖中。這樣每個程式庫模組就能進行跨程式庫模組導覽,瀏覽至這些常用目的地。

在上述範例中,動作指定的導覽目的地為 @id/settings_nav_graph。這個 ID 是指隨附圖表 @navigation/settings_navigation. 中定義的目的地。

應用程式模組中的頂層導覽

導覽元件含有 NavigationUI 類別。這個類別包含多種靜態方法,用於管理頂端應用程式列、導覽匣和底部導覽的導覽行為。如果應用程式的頂層目的地包含由程式庫模組提供的 UI 元素,那麼自然要將頂層導覽和 UI 元素放置在 app 模組中。由於應用程式模組依附協力程式庫模組,因此其所有目的地都可透過應用程式模組中定義的程式碼存取。這表示如果項目 ID 與目的地 ID 相符,您就可以使用 NavigationUI 將目的地連結至選單項目

在圖 2 中,範例 app 模組的主要活動中定義了 BottomNavigationView。選單中的選單項目 ID 與程式庫圖表的導覽圖 ID 相符:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/list_nav_graph"
        android:icon="@drawable/ic_list"
        android:title="List"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/favorites_nav_graph"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/settings_nav_graph"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="ifRoom" />
</menu>

如要讓 NavigationUI 處理底部導覽,請透過主活動類別中的 onCreate() 呼叫 setupWithNavController(),如以下範例所示:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    NavHostFragment navHostFragment =
            (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);

    NavigationUI.setupWithNavController(bottomNav, navController);
}

設定好這個程式碼後,當使用者點選底部導覽項目時,NavigationUI 就會瀏覽至適當的程式庫圖表。

請注意,一般來說,最好不要讓應用程式模組硬性依附內嵌在程式庫模組導覽圖深處的特定目的地。在大部分情況下,您應該要讓應用程式模組只知道任何內嵌或隨附導覽圖的進入點 (這也適用於程式庫模組以外的情況)。如要連結至程式庫導覽圖深處的目的地,建議您使用深層連結。如要讓程式庫瀏覽至其他程式庫導覽圖中的目的地,深層連結也是唯一的做法。

跨程式庫模組導覽

在編譯期間,獨立程式庫模組看不到彼此,因此您無法使用 ID 前往其他模組中的目的地。請改為使用深層連結,直接瀏覽至與隱式深層連結相關聯的目的地。

延續上述範例,如要從清單模組中的某個按鈕瀏覽至設定模組巢狀結構中的目的地,您可以在設定導覽圖中新增目的地的深層連結,如下所示:

<?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"
    android:id="@+id/settings_nav_graph"
    app:startDestination="@id/settings_fragment_one">

    ...

    <fragment
        android:id="@+id/settings_fragment_two"
        android:name="com.example.google.login.SettingsFragmentTwo"
        android:label="@string/settings_fragment_two" >

        <deepLink
            app:uri="android-app://example.google.app/settings_fragment_two" />
    </fragment>
</navigation>

接著在清單片段中將以下程式碼新增至按鈕的 onClickListener

Kotlin

button.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("android-app://example.google.app/settings_fragment_two".toUri())
        .build()
    findNavController().navigate(request)
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        NavDeepLinkRequest request = NavDeepLinkRequest.Builder
            .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two"))
            .build();
        NavHostFragment.findNavController(this).navigate(request);
    }
});

與使用動作或目的地 ID 進行瀏覽不同,您可以瀏覽至所有圖表中的任何 URI,甚至可以跨模組進行瀏覽。

使用 URI 進行瀏覽時,系統「不會」重設返回堆疊。這個行為與明確深層連結導覽不同。在明確深層連結導覽流程中,返回堆疊會在瀏覽時遭取代。