0
1

navigation

Posted at

Android StudioのKotlinで、Bottom Navigationを使用している場合、各タブごとに独立したナビゲーションスタックを管理することが可能です。これを実現するための一般的なアプローチは、各タブごとに別々のNavHostFragmentを使用する方法です。

以下にその実装方法の一例を示します。

  1. 各タブにNavHostFragmentを配置するレイアウトを作成する:
<!-- res/layout/activity_main.xml -->
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/nav_host_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:menu="@menu/bottom_nav_menu" />
</LinearLayout>
  1. 各NavHostFragmentをレイアウトファイルとして定義する:
<!-- res/layout/nav_host_home.xml -->
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nav_host_fragment_home"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="false"
    app:navGraph="@navigation/nav_graph_home" />
<!-- res/layout/nav_host_dashboard.xml -->
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nav_host_fragment_dashboard"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="false"
    app:navGraph="@navigation/nav_graph_dashboard" />
  1. Activityで各NavHostFragmentを切り替えるロジックを実装する:
// MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var bottomNavigationView: BottomNavigationView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bottomNavigationView = findViewById(R.id.bottom_navigation_view)

        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.nav_host_fragment_container, NavHostFragment.create(R.navigation.nav_graph_home))
                .commit()
        }

        bottomNavigationView.setOnItemSelectedListener { item ->
            when (item.itemId) {
                R.id.navigation_home -> {
                    switchToFragment(R.navigation.nav_graph_home)
                    true
                }
                R.id.navigation_dashboard -> {
                    switchToFragment(R.navigation.nav_graph_dashboard)
                    true
                }
                R.id.navigation_notifications -> {
                    switchToFragment(R.navigation.nav_graph_notifications)
                    true
                }
                else -> false
            }
        }
    }

    private fun switchToFragment(navGraphId: Int) {
        val existingFragment = supportFragmentManager.findFragmentByTag(navGraphId.toString())
        if (existingFragment != null) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.nav_host_fragment_container, existingFragment)
                .commit()
        } else {
            val navHostFragment = NavHostFragment.create(navGraphId)
            supportFragmentManager.beginTransaction()
                .replace(R.id.nav_host_fragment_container, navHostFragment, navGraphId.toString())
                .commit()
        }
    }
}
  1. BottomNavigationViewのメニューリソースを作成する:
<!-- res/menu/bottom_nav_menu.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home"
        android:title="Home" />
    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard"
        android:title="Dashboard" />
    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications"
        android:title="Notifications" />
</menu>

このアプローチにより、各タブのナビゲーションスタックが独立して管理されるため、特定のフラグメントに戻る際にpopBackStackを使用しても他のタブには影響を与えません。

上記の実装を使用すると、各タブに独立したナビゲーションスタックを持たせることができます。ただし、標準的なナビゲーション実装では、タブ間のナビゲーションスタックを統一して管理することはできません。

質問のように、以下のシナリオを実現するには追加のカスタマイズが必要です:

  1. タブAの画面1 → タブAの画面2
  2. タブBの画面1 → タブBの画面2
  3. タブCの画面1
  4. タブAの画面2に戻る
  5. タブBの画面2に戻る

各タブが独立したナビゲーションスタックを持つ場合、以下のような拡張が必要です:

  1. ナビゲーションスタックの管理を拡張する: 各タブのナビゲーションスタックを管理するために、FragmentManagerのバックスタックを直接操作する。
  2. タブごとのスタックを保存する: 各タブのナビゲーションスタックの状態を保存・復元するロジックを実装する。

ここに具体的な実装例を示します:

// MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var bottomNavigationView: BottomNavigationView
    private val fragmentStacks = mutableMapOf<Int, Stack<Fragment>>()
    private var currentTabId: Int = R.id.navigation_home

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bottomNavigationView = findViewById(R.id.bottom_navigation_view)
        bottomNavigationView.setOnItemSelectedListener { item ->
            switchToTab(item.itemId)
            true
        }

        if (savedInstanceState == null) {
            initializeStacks()
            switchToTab(R.id.navigation_home)
        }
    }

    private fun initializeStacks() {
        fragmentStacks[R.id.navigation_home] = Stack()
        fragmentStacks[R.id.navigation_dashboard] = Stack()
        fragmentStacks[R.id.navigation_notifications] = Stack()
    }

    private fun switchToTab(tabId: Int) {
        if (tabId == currentTabId) return

        val fragmentManager = supportFragmentManager
        val currentStack = fragmentStacks[currentTabId]
        val newStack = fragmentStacks[tabId]

        fragmentManager.beginTransaction().apply {
            currentStack?.lastElement()?.let { hide(it) }
            newStack?.lastElement()?.let { show(it) }
            commit()
        }

        currentTabId = tabId
    }

    fun pushFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val currentStack = fragmentStacks[currentTabId]

        fragmentManager.beginTransaction().apply {
            currentStack?.lastElement()?.let { hide(it) }
            add(R.id.nav_host_fragment_container, fragment)
            addToBackStack(null)
            commit()
        }

        currentStack?.push(fragment)
    }

    override fun onBackPressed() {
        val currentStack = fragmentStacks[currentTabId]
        if (currentStack != null && currentStack.size > 1) {
            val fragmentManager = supportFragmentManager
            fragmentManager.beginTransaction().apply {
                currentStack.pop()?.let { remove(it) }
                currentStack.lastElement()?.let { show(it) }
                commit()
            }
        } else {
            super.onBackPressed()
        }
    }
}

説明:

  1. Fragmentの管理: 各タブのフラグメントスタックをfragmentStacksで管理します。
  2. タブの切り替え: タブを切り替える際に、現在のタブのフラグメントを非表示にし、次のタブの最後のフラグメントを表示します。
  3. フラグメントのプッシュ: 新しいフラグメントを追加する際に、現在のフラグメントを非表示にし、バックスタックに追加します。
  4. 戻るボタンの処理: 現在のタブのスタックを管理し、スタックからポップして表示を更新します。

このようにして、各タブのナビゲーションスタックを独立して管理しながら、ユーザーがタブを切り替える際に適切なスタックの状態を維持できます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1