Android StudioのKotlinで、Bottom Navigationを使用している場合、各タブごとに独立したナビゲーションスタックを管理することが可能です。これを実現するための一般的なアプローチは、各タブごとに別々のNavHostFragmentを使用する方法です。
以下にその実装方法の一例を示します。
- 各タブに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>
- 各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" />
- 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()
}
}
}
- 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
を使用しても他のタブには影響を与えません。
上記の実装を使用すると、各タブに独立したナビゲーションスタックを持たせることができます。ただし、標準的なナビゲーション実装では、タブ間のナビゲーションスタックを統一して管理することはできません。
質問のように、以下のシナリオを実現するには追加のカスタマイズが必要です:
- タブAの画面1 → タブAの画面2
- タブBの画面1 → タブBの画面2
- タブCの画面1
- タブAの画面2に戻る
- タブBの画面2に戻る
各タブが独立したナビゲーションスタックを持つ場合、以下のような拡張が必要です:
-
ナビゲーションスタックの管理を拡張する: 各タブのナビゲーションスタックを管理するために、
FragmentManager
のバックスタックを直接操作する。 - タブごとのスタックを保存する: 各タブのナビゲーションスタックの状態を保存・復元するロジックを実装する。
ここに具体的な実装例を示します:
// 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()
}
}
}
説明:
-
Fragmentの管理: 各タブのフラグメントスタックを
fragmentStacks
で管理します。 - タブの切り替え: タブを切り替える際に、現在のタブのフラグメントを非表示にし、次のタブの最後のフラグメントを表示します。
- フラグメントのプッシュ: 新しいフラグメントを追加する際に、現在のフラグメントを非表示にし、バックスタックに追加します。
- 戻るボタンの処理: 現在のタブのスタックを管理し、スタックからポップして表示を更新します。
このようにして、各タブのナビゲーションスタックを独立して管理しながら、ユーザーがタブを切り替える際に適切なスタックの状態を維持できます。