Material 3 Expressive の実装例の紹介シリーズです。
今回は Navigation rail を紹介します。
(androidx.compose.material3:material3:1.4.0-alpha15
時点での内容になります。)
Material 3 Expressive での変更点
- これまでの Navigaton rail は非推奨になり、Collapsed と Expanded の 2 パターンが追加され、Collapsed と Expanded の切り替えが可能
- これまでの Navigation rail の置き換え先には Collapsed
- Navigation drawer の置き換え先として Expanded
- Expanded な Navigation rail には Non-modal と Modal の 2 パターン
- 選択中のアイテムのラベルの色が On surface variant から Secondary に変更
実装
Collapsed な Navigation rail
Material 3 Expressive では NavigationRail
ではなくて WideNavigationRail
を使用します。
WideNavigationRail {
items.forEachIndexed { index, item ->
WideNavigationRailItem(
icon = {
Icon(
painter = painterResource(item.icon),
contentDescription = null
)
},
label = { Text(item.label) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}

Expanded で Non-modal な Navigation rail
WideNavigationRailState
に WideNavigationRailValue.Expanded
を設定しつつ、WideNavigationRailItem
にも railExpanded = true
を設定することで Expanded な Navigation rail が表示できます。
WideNavigationRail(
state = rememberWideNavigationRailState(initialValue = WideNavigationRailValue.Expanded)
) {
items.forEachIndexed { index, item ->
WideNavigationRailItem(
railExpanded = true,
icon = {
Icon(
painter = painterResource(item.icon),
contentDescription = null
)
},
label = { Text(item.label) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}

Collapsed と Expanded のシームレスな切り替え
WideNavigationRailState
の collapse()
/ expand()
を呼び出すことで Navigation rail のシームレスな開閉を実装することができます。
val state = rememberWideNavigationRailState()
val scope = rememberCoroutineScope()
Row {
WideNavigationRail(
state = state,
header = {
IconButton(
modifier = Modifier
.padding(start = 24.dp)
.semantics {
stateDescription =
if (state.currentValue == WideNavigationRailValue.Expanded) {
"Expanded"
} else {
"Collapsed"
}
},
onClick = {
scope.launch {
if (state.targetValue == WideNavigationRailValue.Expanded) {
state.collapse()
} else {
state.expand()
}
}
}
) {
if (state.targetValue == WideNavigationRailValue.Expanded) {
Icon(painterResource(R.drawable.ic_menu_open), "Collapse rail")
} else {
Icon(painterResource(R.drawable.ic_menu), "Expand rail")
}
}
}
) {
items.forEachIndexed { index, item ->
WideNavigationRailItem(
railExpanded = state.targetValue == WideNavigationRailValue.Expanded,
icon = {
Icon(
painter = painterResource(item.icon),
contentDescription = null
)
},
label = { Text(item.label) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
...
}

Modal な Navigation rail
Modal な Navigation rail を使う時は WideNavigationRail
の代わりに ModalWideNavigationRail
を使用します。
val state = rememberWideNavigationRailState()
val scope = rememberCoroutineScope()
Row {
ModalWideNavigationRail(
state = state,
header = {
IconButton(
modifier = Modifier
.padding(start = 24.dp)
.semantics {
stateDescription =
if (state.currentValue == WideNavigationRailValue.Expanded) {
"Expanded"
} else {
"Collapsed"
}
},
onClick = {
scope.launch {
if (state.targetValue == WideNavigationRailValue.Expanded) {
state.collapse()
} else {
state.expand()
}
}
}
) {
if (state.targetValue == WideNavigationRailValue.Expanded) {
Icon(painterResource(R.drawable.ic_menu_open), "Collapse rail")
} else {
Icon(painterResource(R.drawable.ic_menu), "Expand rail")
}
}
}
) {
items.forEachIndexed { index, item ->
WideNavigationRailItem(
railExpanded = state.targetValue == WideNavigationRailValue.Expanded,
icon = {
Icon(
painter = painterResource(item.icon),
contentDescription = null
)
},
label = { Text(item.label) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
...
}
