2022年11月30日水曜日
2022年11月25日金曜日
BottomNavigation / NavigationBar が消える時にレイアウトが下にずれるのを防ぎたい
このように Compose Navigation で BottomNavigation / NavigationBar を表示しない画面に遷移したときに、現在のレイアウトが下にずれてしまうことがあります。
これを防ぐ方法を紹介します。
これを防ぐ方法を紹介します。
1. NavHost に innerPadding を使わない
- Scaffold(
- ...
- ) { innerPadding ->
- NavHost(
- modifier = Modifier.padding(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()) {
- ...
- }
- }
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 {
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 = 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,
- )
- }
- }
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")
- }
- }
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
- ) {
- ...
- }
- }
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)