BottomNavigation(Material3のNavigationBar)は、アプリケーションの3〜5つのメイン画面間のナビゲーションを提供するMaterial Designコンポーネントです。Jetpack Composeでは、下部バーはNavigationBarまたはBottomNavigationを介して実装され、各項目はアイコンとテキストラベルで表現されます。Google Material Design(2025)によると、BottomNavigationはiOSとAndroidの主要なナビゲーションパターンとして推奨されています。パネルはWindowInsetsを介してシステムナビゲーション(Gesture Navigation)の高さに自動的に適応します。Material3のNavigationBarはダイナミックカラー、バッジ、アダプティブマージンをサポートしています。
重要ポイント
BottomNavigationは、3〜5つのメニュー項目を表示する下部ナビゲーションバー用のMaterial Designコンポーネントです。Jetpack Composeでは、BottomNavigation(Material2)およびNavigationBar(Material3)のコンポーザブル関数を介して実装されます。バーは画面の下部に配置され、アプリケーションの主要セクションへのクイックアクセスを提供します。
BottomNavigationの主な目的は、画面レベルの遷移先間の一次ナビゲーションを整理することです。各パネル項目は個別の画面(ホーム、検索、プロファイル)を表します。タップすると画面のメインエリアのコンテンツが切り替わります。BottomNavigationはネストされたナビゲーションには対応していません — その場合は上部のタブが使用されます。
Material Designガイドライン(2025)によると、下部バーにはアプリケーションの任意のポイントからアクセス可能なセクションのみを含める必要があります。子画面でバーを非表示にする(詳細ページを開く場合など)ことは標準的な慣行です。Composeでは、BottomNavigationの表示はScaffoldの状態または現在のNavControllerルートに基づく条件付きレンダリングによって制御されます。
Material3では、レガシーなBottomNavigationの代わりにNavigationBarを使用することを推奨します。NavigationBarにはダイナミックカラー、WindowInsetsサポート、改善されたプレスアニメーションが含まれています。Googleによると、NavigationBarは将来を見据えたコンポーネントであり、M2のBottomNavigationは下位互換性のためだけに残されています。
各NavigationBar項目はNavigationBarItemコンポーザブルを介して実装されます。パラメータ:icon(アイコン)、label(テキストラベル)、selected(選択状態)、onClick(コールバック)。NavigationBarItemは自動的に選択色(primary)を適用し、アクティブ化時にアイコンサイズ変更をアニメーションします。
@Composable
fun BottomNavBar() {
val items = listOf(
Screen.Home,
Screen.Search,
Screen.Profile
)
var selectedItem by remember { mutableIntStateOf(0) }
NavigationBar {
items.forEachIndexed { index, item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}
NavigationBarItemのアイコンは、選択状態では塗りつぶし(Filled)、非選択状態ではアウトライン(Outlined)にする必要があります。Material Iconsは両方のセットを提供しています:Icons.Filled.HomeとIcons.Outlined.Home。デフォルトでは、NavigationBarItemは選択時に塗りつぶしバリアントを表示します。異なる表示が必要な場合は、条件付きでアイコンを渡します:if (selected) Filled.X else Outlined.X。
テキストラベルの長さは最大12文字(ラテン文字)または8文字(キリル文字)にする必要があります。長いラベルは省略記号で切り捨てられます。ラベルにはmaxLines = 1とellipsis = TextTruncation.Ellipsisを使用します。ラベルを常に表示する必要がある場合は、alwaysShowLabel = trueパラメータを削除します(デフォルトはtrue)。
| パラメータ | 型 | 説明 |
|---|---|---|
| icon | @Composable () -> Unit | パネル項目のアイコン |
| label | @Composable (() -> Unit)? | アイコン下のテキストラベル |
| selected | Boolean | 選択項目フラグ |
| onClick | () -> Unit | クリック時のコールバック |
| badge | @Composable (() -> Unit)? | 通知数付きバッジ |
selectedItemを直接更新してもナビゲーションは実行されません — 画面切り替えにはNavControllerとの統合が必要です。典型的なパターン:NavigationBarItemのonClickがnavController.navigate(route)を呼び出し、selectedItemは現在のNavControllerルートから決定されます。
NavControllerは現在のバックスタックを保存します。どのパネル項目が選択されているかを判断するには、現在のルート(navController.currentBackStackEntryAsState().value?.destination?.route)を項目ルートと比較します。ルートが一致した場合、その項目は選択されたと見なされます。これにより状態の重複が排除されます。
@Composable
fun MainScreen(navController: NavController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Scaffold(
bottomBar = {
NavigationBar {
items.forEach { item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { /* content */ }
}
navigate()の主要設定:popUpTo(startDestination)はタブ切り替え時のバックスタック蓄積を防ぎ、launchSingleTopはスタック内のルート重複を防ぎ、restoreStateは以前に訪問したタブの状態を復元します。これらのパラメータがないと、クリックのたびにスタックに新しいエントリが作成され、戻るボタンの動作が不正になります。
Material3(M3)は、Material2のBottomNavigationの代替としてNavigationBarとNavigationBarItemを提供します。M3の主な利点:ダイナミックカラー、アダプティブ高さ、全幅リップルアニメーション、システムナビゲーション用のWindowInsetsサポート。
ダイナミックカラーはデバイスの壁紙(Android 12+)に基づいてNavigationBarの色を自動的に選択します。アクティブな項目はprimary色で着色され、非アクティブな項目はonSurfaceVariantで着色されます。デフォルトでは、NavigationBarはModifier.navigationBarsPadding()を介してシステムナビゲーションの上に配置されます。手動制御にはwindowInsetsパラメータを使用します。
M3のNavigationBarはアクティブ項目の幅を自動的にアニメーションします — アイコンが左に移動し、ラベルが拡張します。アニメーションは300 ms続き、標準のComposeイージングを使用します。この動作が望ましくない場合は、NavigationBarItemでalwaysShowLabel = trueを介して常にラベルを表示します。
色をオーバーライドするには、colorsパラメータにNavigationBarColorsを渡します。デフォルトではNavigationBarDefaults.colors()が使用されますが、カスタム値を設定できます:containerColor(バーの背景)、contentColor(アイコンの色)、indicatorColor(アクティブ項目の背景)。M2で色がBottomNavigationDefaultsを介して設定されていたのとは異なり、M3は統一されたNavColorsインターフェースを使用します。
プロジェクトがM2(androidx.compose.material)のBottomNavigationを使用している場合、GoogleはM3のNavigationBarへの移行を推奨します。移行にはインポートの置き換え、パラメータの更新、@OptIn(ExperimentalMaterial3Api::class)の追加が含まれます。M2のBottomNavigationは非推奨と見なされ、新機能は提供されません。
NavigationBarの状態は、現在選択されている項目のインデックスを保存します。単純なケースでは、remember { mutableIntStateOf(0) }が使用されます。ただし、本番アプリケーションでは、インデックスはナビゲーショングラフと同期する必要があり、ローカルなUI状態であってはなりません。
状態管理の主要なアプローチ:NavControllerベース(推奨)とViewModelベース。最初のケースでは、selectedItemは現在のNavControllerルートから計算されます。2番目のケースでは、ViewModelが現在の画面を保存し、NavigationBarItemがViewModelからStateFlowを読み取ります。NavControllerベースはよりシンプルで、ViewModelベースは複雑なロジック(認証、A/Bテスト)に対してより多くの制御を提供します。
saveStateとrestoreStateを使用する場合、NavControllerは各タブ内のLazyColumnおよびその他のコンポーネントの状態を自動的に保存および復元します。これにより、スクロール位置を保存するためのViewModelが不要になります。ただし、保存は入力フィールドには適用されません — これらにはSavedStateHandleまたはViewModelが必要です。
Badgeは、NavigationBarItemのアイコンに表示されるコンパクトな通知インジケーターです。Material3では、バッジはBadgedBoxを介して実装され、アイコンをラップしてバッジを右上隅に配置します。バッジには数値(通知数)またはドット(単純な存在インジケーター)を含めることができます。
@Composable
fun HomeItem(unreadCount: Int) {
NavigationBarItem(
icon = {
BadgedBox(badge = {
if (unreadCount > 0) {
Badge { Text(unreadCount.toString()) }
}
}) {
Icon(Icons.Filled.Home, contentDescription = "ホーム")
}
},
label = { Text("Home") },
selected = false,
onClick = { }
)
}
BadgedBoxは自動的にBadgeを子要素の右上隅に配置します。カウントが99を超える場合、Badgeは"99+"を表示します。ゼロ時にバッジを非表示にするには、if (count > 0)を使用します。Material3のBadgeはデフォルトで白いテキストのerror色(赤)を持ちます。BadgeDefaults.colors(containerColor, contentColor)でカスタマイズします。
Material2ではバッジは存在せず — Canvasを介して手動で描画するか、サードパーティのライブラリを使用する必要がありました。Material3はネイティブコンポーネントでこの問題を解決しました。Android Developers(2025)によると、BadgedBoxはNavigationBarを使用するアプリの40%で使用されており、そのうち60%が未読メッセージを表示しています。
よくある質問
Material Designは3〜5項目を推奨しています。3つ未満は実用的ではありません(代わりにタブを使用してください)。5つを超えるとラベルの可読性とタッチターゲットサイズが低下します。5つ以上のセクションが必要な場合は、Navigation Rail(タブレット)またはDrawerを使用してください。
バーを表示するルートのリストを定義します(通常はルートルート)。ScaffoldのbottomBarで、currentRouteがbottomNavRoutesにある場合のみNavigationBarを渡します。代替として、bottomBarなしのネストされた画面用に別のグラフを持つNavHostを使用します。
Material3のNavigationBarはダイナミックカラー、BadgedBox、WindowInsets、アニメーション付きアクティブ項目幅をサポートしています。Material2のBottomNavigationはこれらの機能のないレガシーAPIです。Googleは新しいプロジェクトにはNavigationBarを使用し、既存のプロジェクトを移行することを推奨しています。
問題はnavigate()にsaveStateとrestoreStateがないことです。これらがないと、NavControllerは前のタブの状態を保存しません。LazyColumn、スクロール位置、入力フィールドを保持するには、NavOptionsにpopUpTo(startDestination) { saveState = true }とrestoreState = trueを追加してください。
切り替えアニメーションはBottomNavigationではなくNavHostレベルで制御されます。カスタムアニメーションでcomposable(route, enterTransition, exitTransition)を追加します。BottomNavigationでは、300msのフェードアニメーション(fadeIn + fadeOut)が一般的です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。