2022年11月30日水曜日
2022年11月25日金曜日
BottomNavigation / NavigationBar が消える時にレイアウトが下にずれるのを防ぎたい
このように Compose Navigation で BottomNavigation / NavigationBar を表示しない画面に遷移したときに、現在のレイアウトが下にずれてしまうことがあります。
これを防ぐ方法を紹介します。
これを防ぐ方法を紹介します。
1. NavHost に innerPadding を使わない
Scaffold(
...
) { innerPadding ->
NavHost(
modifier = Modifier.padding(innerPadding), // これはダメ
...
) {
...
NavHost の中の Composable で innerPadding を使うようにします。
Scaffold(
...
) { innerPadding ->
val modifier = Modifier.padding(innerPadding)
NavHost(
...
) {
composable(Destination.Home.route) {
HomeScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier, // こうする
)
}
...
@Composable
private fun HomeScreen(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize()) {
...
}
}
このとき currentBackStack が null でも BottomNavigation / NavigationBar が表示されるようにしないと bottom padding が 0 になる問題があるので注意してください。
val navBackStackEntry by navController.currentBackStackEntryAsState()
// ここで ?: Destination.Home.route をつけないと HomeScreen() に渡される Modifier で bottom padding が 0 になってしまう
val currentRoute = navBackStackEntry?.destination?.route ?: Destination.Home.route
val routes = remember { Destination.values().map { it.route } }
val showNavigationBar = currentRoute in routes
if (showNavigationBar) {
BottomNavigation {
...
2. BottomNavigation / NavigationBar が表示される画面を nested graph にする
これを
NavHost(
navController = navController,
startDestination = Destination.Home.route
) {
composable(Destination.Home.route) {
HomeScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier,
)
}
composable(Destination.Settings.route) {
SettingsScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier,
)
}
composable("profile") {
ProfileScreen(
modifier = modifier,
)
}
}
こうする
NavHost(
navController = navController,
startDestination = "main"
) {
// BottomNavigation / NavigationBar が表示される画面を nested graph にする
navigation(
route = "main",
startDestination = Destination.Home.route
) {
composable(Destination.Home.route) {
HomeScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier,
)
}
composable(Destination.Settings.route) {
SettingsScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier,
)
}
}
composable("profile") {
ProfileScreen(
modifier = modifier,
)
}
}
これで、下にずれなくなります。
最後に全体のコードを置いておきます。
enum class Destination(val title: String, val route: String, val imageVector: ImageVector) {
Home("Home", "home", Icons.Filled.Home),
Settings("Settings", "settings", Icons.Filled.Settings)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
topBar = {
TopAppBar(
title = {
Text("Main")
}
)
},
bottomBar = {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val routes = remember { Destination.values().map { it.route } }
val currentRoute = navBackStackEntry?.destination?.route ?: Destination.Home.route
val showNavigationBar = currentRoute in routes
if (showNavigationBar) {
NavigationBar {
Destination.values().forEach { destination ->
NavigationBarItem(
icon = { Icon(destination.imageVector, contentDescription = null) },
label = { Text(destination.title) },
selected = currentRoute == destination.route,
onClick = {
navController.navigate(destination.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
}
) { innerPadding ->
val modifier = Modifier.padding(innerPadding)
NavHost(
navController = navController,
startDestination = "main"
) {
navigation(
route = "main",
startDestination = Destination.Home.route
) {
composable(Destination.Home.route) {
HomeScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier,
)
}
composable(Destination.Settings.route) {
SettingsScreen(
onClick = {
navController.navigate("profile")
},
modifier = modifier,
)
}
}
composable("profile") {
ProfileScreen(
modifier = modifier,
)
}
}
}
}
@Composable
private fun HomeScreen(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize()) {
Text("home")
Button(onClick = onClick, modifier = Modifier.align(Alignment.BottomCenter)) {
Text("button")
}
}
}
@Composable
private fun SettingsScreen(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize()) {
Text("settings")
Button(onClick = onClick, modifier = Modifier.align(Alignment.BottomCenter)) {
Text("button")
}
}
}
@Composable
private fun ProfileScreen(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize()) {
Text("profile")
}
}
2022年11月15日火曜日
Material 3 で TopAppBar の shadow がなくなったけど、コンテンツとの境界はどう表現する?
Material 2 では TopAppBar に shadow がついていて、スクロールアウトするコンテンツとの境界として機能していました。
Material 3 では shadow はつきません。そのため、Material 2 のコードをそのまま Material 3 に移行すると、コンテンツの境界表現がなくなってしまいます。 Material 3 では shadow の代わりに色のオーバーレイでコンテンツと分離します。
そのために TopAppBarScrollBehavior を使います。
Material 3 では shadow はつきません。そのため、Material 2 のコードをそのまま Material 3 に移行すると、コンテンツの境界表現がなくなってしまいます。 Material 3 では shadow の代わりに色のオーバーレイでコンテンツと分離します。
そのために TopAppBarScrollBehavior を使います。
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold(
topBar = {
TopAppBar(
title = {
...
},
scrollBehavior = scrollBehavior
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
LazyColumn(
contentPadding = it
) {
...
}
}
TopAppBarScrollBehavior として
- PinnedScrollBehavior
- EnterAlwaysScrollBehavior
- ExitUntilCollapsedScrollBehavior
TopAppBarDefaults.pinnedScrollBehavior()
TopAppBarDefaults.enterAlwaysScrollBehavior()
ExitUntilCollapsedScrollBehavior
登録:
投稿 (Atom)